文档章节

java 、HashMap 和单例

一宁
 一宁
发布于 2014/06/15 19:15
字数 1130
阅读 126
收藏 3

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

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.


© 著作权归作者所有

共有 人打赏支持
一宁
粉丝 12
博文 19
码字总数 16414
作品 0
西安
去投资银行面试会遇到的10个Java问题

本文由ImportNew -大瓜细瓜 翻译自dzone。欢迎加入翻译小组。转载请见文末要求。 很多Java开发人员会到巴克莱、瑞士信贷、花旗等投资银行申请Java开发职位,但他们中很多人都不知道面试时会遇...

ImportNew
07/25
0
0
第三章 spring-bean之DefaultSingletonBeanRegistry(3)

前言 SingletonBeanRegistry是一个非常重要的接口,用于注册,获得,管理singleton对象。 SingletonBeanRegistry目前唯一的实现是DefaultSingletonBeanRegistry,DefaultSingletonBeanRegis...

鸟菜啊
07/18
0
0
从高频笔/面试题思考Android学习/进阶路线(Java篇)

写在前面 标题谈进阶,属实有一些夸大。 我一直在思考什么样的文章才是一篇好文章,我的定义是首先要有人看,其次重要的是内部有价值。所以针对于这个出发点,我决定从大家比较关注的面试题入...

MDove
07/24
0
0
Java ThreadLocal的用法解析

简介 java中经常使用ThreadLocal作为处理高并发访问的可选手段,ThreadLocal并不是一个线程,而是”以线程为作用域“的一种面向对象的数据结构。其用法的也有着让人难以理解的怪异性。 代码实...

IamOkay
2014/10/25
0
0
为什么我墙裂建议大家使用枚举来实现单例。

关于单例模式,我的博客中有很多文章介绍过。作为23种设计模式中最为常用的设计模式,单例模式并没有想象的那么简单。因为在设计单例的时候要考虑很多问题,比如线程安全问题、序列化对单例的...

06/10
0
0

没有更多内容

加载失败,请刷新页面

加载更多

【大福利】极客时间专栏返现二维码大汇总

我已经购买了如下专栏,大家通过我的二维码你可以获得一定额度的返现! 然后,再给大家来个福利,只要你通过我的二维码购买,并且关注了【飞鱼说编程】公众号,可以加我微信或者私聊我,我再...

飞鱼说编程
今天
1
0
Spring5对比Spring3.2源码之容器的基本实现

最近看了《Spring源码深度解析》,该书是基于Spring3.2版本的,其中关于第二章容器的基本实现部分,目前spring5的实现方式已有较大改变。 Spring3.2的实现: public void testSimpleLoad(){...

Ilike_Java
今天
1
0
【王阳明心学语录】-001

1.“破山中贼易,破心中贼难。” 2.“夫万事万物之理不外于吾心。” 3.“心即理也。”“心外无理,心外无物,心外无事。” 4.“人心之得其正者即道心;道心之失其正者即人心。” 5.“无...

卯金刀GG
今天
2
0
OSChina 周三乱弹 —— 我们无法成为野兽

Osc乱弹歌单(2018)请戳(这里) 【今日歌曲】 @ _刚刚好: 霸王洗发水这波很骚 手机党少年们想听歌,请使劲儿戳(这里) hahahahahahh @嘻酱:居然忘了喝水。 让你喝可乐的话, 你准忘不了...

小小编辑
今天
10
0
vm GC 日志 配置及查看

-XX:+PrintGCDetails 打印 gc 日志 -XX:+PrintTenuringDistribution 监控晋升分布 -XX:+PrintGCTimeStamps 包含时间戳 -XX:+printGCDateStamps 包含时间 -Xloggc:<filename> 可以将数据保存为......

Canaan_
昨天
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部