• java 、HashMap 和单例


    前段时间在项目中遇到一个问题。当多个系统同时运行时,大部分系统能够良好运转,部分却卡死在了启动界面。以下是我解决该问题的步骤和总结:

     
    1、复现问题。重新走了一遍出问题的过程,发现问题的确存在。说明这个问题不是偶然发生。
    2、看日志。确定问题是必然发生之后,开始查看日志,发现日志中有问题的系统状态一直不正常。一直处于任务过期的状态。一个系统对应一个任务,任务过期之后,系统就处于卡死状态。系统的逻辑是这样的:当启动系统的时候,会发起多个请求,每个请求会产生一个任务,同时将这些任务写到缓存(HashMap)和数据库。任务的状态(包括数据库和缓存)会随着任务的进度而发生改变。
     
     
    任务过期意味着该任务已经执行完毕或者从来没有这个任务。
    如果说任务已经执行完毕导致这个问题的话,这个是不可能的。因为对于每个任务,当他执行成功或者失败时,垃圾回收器会在15分钟后对任务进行清理。事实上,当我们一开启系统时,就观察到该系统对应的任务在数据库中存在,但是在缓存中却不存在!就是说,当我们从HashMap 中获取相应的任务时,获取到的值是不存在的!为什么获取到的值会不存在呢?这可能有两种原因:
    (1)任务根本就没有写入缓存;
    (2)任务写入缓存后很快被清理掉了;
    但是根据以上的分析,任务被很快清理掉是不可能的。因为至少得在15分钟之后,才能清理。那就只有第一种可能了:任务根本没有写入缓存!
     
    开始着手看代码。发现写入缓存的关键一行代码:
    MyMap. getInstance().put( taskId"hello" );
     
    继续跟踪MyMap,主要的类相关内容如下:
     
    public class MyMap {
         
          private Map<Integer, Object> map = new HashMap<Integer, Object>();
          private Object lock = new Object();
         
          private static MyMap instance new MyMap();
          private MyMap(){}
          public static MyMap getInstance() {
               if (instance == null) {
                   instance new MyMap();
               }
               return instance ;
          }
          public void put(Integer taskId, String name) {
               synchronized (lock ) {
                   map.put(taskId, name);
               }
          }
         
          public Map<Integer, Object> getMap() {
               return map ;
          }
     
    }
     
     
    该类使用单例模式,使用HashMap来保存所有的任务。每次执行一个任务,都会将这个任务写入缓存。然后根据taskId获取相应的任务。这段代码看起来没有多大问题。
     
    但是在高并发的情况下,这个单例是不安全的:
    public static MyMap getInstance() {
               if (instance == null) {
                   instance new MyMap();
              }
               return instance ;
         }
     
    在多个线程同时请求getInstance时,某个线程,判断instance == null 为true,会继续执行instance = new MyMap(); 
     
    这行代码会先new MyMap(),在heap上分配内存空间,然后将instance 指向该内存地址。在instance 未指向该内存空间时,如果其他线程也调用getInstance时,发现instance == null 为真,也会执行new MyMap()。这时,不同的线程拿到的就不是同一个实例了。调用put后,会将不同的数据写入到不同对象对应的map中。所以我们拿到的实例有可能是所有线程共享的实例,也有可能是某些线程共享的实例,当然我们就只能获取到部分数据,另外的数据就丢失了。或者说数据依然在某个内存中,但是我们丢失了指向该数据的引用。所以部分任务就这么丢失了,导致系统处于卡死状态。
     
    如何来处理这种不安全的单例呢?
    使用两种方式可以解决:
     
    (1)给getInstance()方法添加关键字synchronized,保证当前只有一个线程执行该方法。
     
    public synchronized static MyMap getInstance() {
               if (instance == null) {
                   instance new MyMap();
               }
               return instance ;
     }
    (2)
    private static MyMap instance = new MyMap();
    private MyMap(){}
    public static MyMap getInstance() {
               return instance ;
    }
     
    第一种方式使用效率较低。第二种方式在类加载时便生成对象。没有使用类的延迟加载。
    另外还有两种方式可以实现:内部静态类和双重校验锁(暂且不讨论)。
     
    通过这两种方式,即可以解决单例模式的线程安全问题。同时,为了提高效率,将缓存从HashMap改为ConcurrentHashMap.
     
     
     
  • 相关阅读:
    Element+Vue.js 选择器常用属性
    常用xml头文件
    【链接】调查显示:超20%美国大学生曾花学生贷款投
    Quartz遇到的问题
    List去重
    SpringDataJPA
    IDEA:Error during artifact deployment. See server log for details.详解
    Quartz定时任务
    多线程条件通行工具——CountDownLatch
    多线程同步工具——Lock
  • 原文地址:https://www.cnblogs.com/benshan/p/3789734.html
Copyright © 2020-2023  润新知