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

 
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.
 
 

java 、HashMap 和单例的更多相关文章

  1. Java设计模式之单例

    一.Java中的单例: 特点: ① 单例类只有一个实例 ② 单例类必须自己创建自己唯一实例 ③ 单例类必须给所有其他对象提供这一实例 二.两种模式: ①懒汉式单例<线程不安全> 在类加载时 ...

  2. Java复习11. 单例编程

    Java复习11. 单例编程 1.最简单的写法,那个方式是线程不安全的 public class Singleton {     private static Singleton instance; ...

  3. Java开发之单例设计模式

    设计模式之单例模式: 一.单例模式实现特点:①单例类在整个应用程序中只能有一个实例(通过私有无参构造器实现):②单例类必须自己创建这个实例并且可供其他对象访问(通过静态公开的访问权限修饰的getIns ...

  4. java基础28 单例集合Collection下的List和Set集合

    单例集合体系: ---------| collection  单例集合的根接口--------------| List  如果实现了list接口的集合类,具备的特点:有序,可重复       注:集合 ...

  5. java基础27 单例集合Collection及其常用方法

    1.集合 集合是存储对象数据的集合容器 1.1.集合比数组的优势 1.集合可以存储任意类型的数据,数组只能存储同一种数据类型的数据    2.集合的长度是变化的,数组的长度是固定的 1.2.数组:存储 ...

  6. Java 多线程之单例设计模式

    转载:https://segmentfault.com/a/1190000007504892 概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍两种:懒汉式单例.饿汉 ...

  7. 如何防止JAVA反射对单例类的攻击?

    在我的上篇随笔中,我们知道了创建单例类有以下几种方式: (1).饿汉式; (2).懒汉式(.加同步锁的懒汉式.加双重校验锁的懒汉式.防止指令重排优化的懒汉式); (3).登记式单例模式; (4).静态 ...

  8. 设计模式(java) 单例模式 单例类

    ·单例类 单实例类,就是这个类只能创建一个对象,保证了对象实例的唯一性. 1.单例模式( Singleton Pattern) 是一个比较简单的模式, 其定义如下:Ensure a class has ...

  9. java中的单例设计模式

    单例模式有一下特点: 1.单例类只能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供 ...

随机推荐

  1. iphone5刷机教程

    如果不想麻烦可以在越狱之后添加源,cydia.china3gpp.com打ios7的补丁就可以了 机器为iphone5 美国sprint有锁版 1. 首先备份需要的程序和数据(把各种缓存的影片删掉再备 ...

  2. 在Windows系统上怎么使用SecureCRT链接Linux AWS EC2 -摘自网络

    在Windows系统上就需要使用SecureCRT,Putty等工具,进行连接.但是AWS提供的XXX.pem文件,需要做一些处理SecureCRT的方法: 1.使用XXX.pem文件生成一个公钥文件 ...

  3. JS中parseint和number的区别

    两者定义的区别 parseInt将字符串(String)类型转为整数类型.Number() 函数把对象(Object)的值转换为数字. parseInt得到的结果是整数或者NaN,而Number得到的 ...

  4. linux杀掉tomcat应用进程。停止tomcat应用

    ps -ef |grep tomcat |grep -v grep |awk '{print $2}' |xrags kill -9

  5. MySQL 5.6学习笔记(数据类型)

    MySQL支持多种数据类型,主要有数值类型.日期/时间类型和字符串类型. 数值数据类型:包括整数类型TINYINT.SMALLINT.MEDIUMINT.INT.BIGINT.浮点小数类型FLOAT的 ...

  6. POJ 1636 Prison rearrangement DFS+0/1背包

    题目链接: id=1636">POJ 1636 Prison rearrangement Prison rearrangement Time Limit: 3000MS   Memor ...

  7. 【Linux运维-集群技术进阶】Nginx+Keepalived+Tomcat搭建高可用/负载均衡/动静分离的Webserver集群

    额.博客名字有点长.. . 前言 最终到这篇文章了,心情是有点激动的. 由于这篇文章会集中曾经博客讲到的全部Nginx功能点.包含主要的负载均衡,还有动静分离技术再加上这篇文章的重点.通过Keepal ...

  8. 菜鸟调错(六)——Hibernate 4.3.x 注解常见错误及解决方案

    编程的过程免不了遇到各种错误,各种问题,而遇到问题,解决问题的这个过程我认为是最让人兴奋的事情.越棘手的问题,解决以后带来的快感也越大.当一个问题你搞了一下午或者一天,甚至几天,当你解决的那一刻你会觉 ...

  9. FLINK 案例分析

    基于Flink流处理的动态实时超大规模用户行为分析 https://zhuanlan.zhihu.com/p/31548501 基于Flink流处理的动态实时超大规模用户行为分析 https://zh ...

  10. vue2.0的生命周期

    生命周期先上图 什么是生命周期 Vue实例有一个完整的生命周期,也就是从开始创建.初始化数据.编译模板.挂载Dom.渲染→更新→渲染.卸载等一系列过程, 我们称这是Vue的生命周期.通俗说就是Vue实 ...