zoukankan      html  css  js  c++  java
  • 【Java】单例模式双重检查锁(double-checked locking)

    双重锁校验单例

      代码如下:

     1 public class DoubleCheckedLock {
     2 
     3     // 使用volatile修饰禁止重排序
     4     private volatile static DoubleCheckedLock instance;
     5 
     6     private DoubleCheckedLock() {
     7         //构造器必须私有  不然直接new就可以创建
     8     }
     9 
    10 
    11     public static DoubleCheckedLock getInstance() {
    12         //第一次判断,假设会有好多线程,如果doubleLock没有被实例化,那么就会到下一步获取锁,只有一个能获取到,
    13         //如果已经实例化,那么直接返回了,减少除了初始化时之外的所有锁获取等待过程
    14         if (instance == null) {
    15             //同步
    16             synchronized (DoubleCheckedLock.class) {
    17                 //第二次判断是因为假设有两个线程A、B,两个同时通过了第一个if,然后A获取了锁,进入然后判断doubleLock是null,
    18                 // 他就实例化了doubleLock,然后他出了锁,这时候线程B经过等待A释放的锁,B获取锁了,
    19                 // 如果没有第二个判断,那么他还是会去new DoubleLock(),再创建一个实例,所以为了防止这种情况,需要第二次判断
    20                 if (instance == null) {
    21                     //下面这句代码其实分为三步:
    22                     //1.开辟内存分配给这个对象
    23                     //2.初始化对象
    24                     //3.将内存地址赋给虚拟机栈内存中的doubleLock变量
    25                     //注意上面这三步,第2步和第3步的顺序是随机的,这是计算机指令重排序的问题
    26                     //假设有两个线程,其中一个线程执行下面这行代码,如果第三步先执行了,就会把没有初始化的内存赋值给doubleLock
    27                     //然后恰好这时候有另一个线程执行了第一个判断if(doubleLock == null),然后就会发现doubleLock指向了一个内存地址
    28                     //这另一个线程就直接返回了这个没有初始化的内存,所以要防止第2步和第3步重排序
    29                     instance = new DoubleCheckedLock();
    30                 }
    31             }
    32         }
    33         return instance;
    34     }
    35 }

    使用volatile修饰变量

      instance = new DoubleCheckedLock();

      创建了一个新对象,这一 行代码可以分解为如下的3行伪代码。

    1 memory = allocate();  // 1:分配对象的内存空间 
    2 ctorInstance(memory); // 2:初始化对象 
    3 instance = memory;    // 3:设置instance指向刚分配的内存地址

      编辑器和处理器会进行代码优化,而其中重要的一点是会将指令进行重排序。

      上边的代码经过重排序后可能会变为

    1 memory = allocate();  // 1:分配对象的内存空间  
    2 instance = memory;    // 3:设置instance指向刚分配的内存地址
    3                       // 注意:此时对象尚未初始化
    4 ctorInstance(memory); // 2:初始化对象

      这样会引起一些问题,打个比方,有一个线程A在创建对象,另一个线程B判断对象是否为空if (instance == null),如果指令被重排序,那么当A尚未初始化对象但已分配内存地址时,若B在做判断,会得到错误的结果。  

      所以,用 volatile 来禁止上述指令的重排序,使B的判断不会出错。

      private volatile static DoubleCheckedLock instance;

      关于volatile关键字的使用参考:【Java多线程】volatile关键字解析(五)

  • 相关阅读:
    NodeJS简介
    SQL学习笔记(1)
    Linux常用命令03
    Linux常用命令02
    Linux常用命令01
    Java接口和抽象类的区别
    Optional解决空指针
    JVM的内存管理机制-转载
    mysql explain详解-转载
    rabbitmq消息处理-转载
  • 原文地址:https://www.cnblogs.com/h--d/p/14175999.html
Copyright © 2011-2022 走看看