博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
双重检查锁定(double-checked locking)与单例模式
阅读量:3562 次
发布时间:2019-05-20

本文共 5105 字,大约阅读时间需要 17 分钟。

单例模式有如下实现方式:
[java]
  1. package com.zzj.pattern.singleton;  
  2.   
  3. public class Singleton {  
  4.     private static Singleton instance;  
  5.   
  6.     private Singleton() {  
  7.     }  
  8.   
  9.     public static Singleton getInstance() {  
  10.         if (instance == null) {  
  11.             instance = new Singleton();  
  12.         }  
  13.         return instance;  
  14.     }  
  15. }  
package com.zzj.pattern.singleton;public class Singleton {    private static Singleton instance;    private Singleton() {    }    public static Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }}

这种方式称为延迟初始化,但是在多线程的情况下会失效,于是使用同步锁,给getInstance() 方法加锁:

[java]
  1. public static synchronized Singleton getInstance() {  
  2.         if (instance == null) {  
  3.             instance = new Singleton();  
  4.         }  
  5.         return instance;  
  6.     }  
public static synchronized Singleton getInstance() {        if (instance == null) {            instance = new Singleton();        }        return instance;    }
同步是需要开销的,我们只需要在初始化的时候同步,而正常的代码执行路径不需要同步,于是有了双重检查加锁(DCL):
[java]
  1. public static Singleton getInstance() {  
  2.         if (instance == null) {  
  3.             synchronized (Singleton.class) {  
  4.                 if (instance == null) {  
  5.                     instance = new Singleton();  
  6.                 }  
  7.             }  
  8.         }  
  9.         return instance;  
  10.     }  
public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                if (instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }
这样一种设计可以保证只产生一个实例,并且只会在初始化的时候加同步锁,看似精妙绝伦,但却会引发另一个问题,这个问题由 指令重排序引起。

指令重排序是为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。JVM规范规定,指令重排序可以在不影响单线程程序执行结果前提下进行。例如 instance = new Singleton() 可分解为如下伪代码:

[java]
  1. memory = allocate();   //1:分配对象的内存空间  
  2. ctorInstance(memory);  //2:初始化对象  
  3. instance = memory;     //3:设置instance指向刚分配的内存地址  
memory = allocate();   //1:分配对象的内存空间ctorInstance(memory);  //2:初始化对象instance = memory;     //3:设置instance指向刚分配的内存地址
但是经过重排序后如下:
[java]
  1. memory = allocate();   //1:分配对象的内存空间  
  2. instance = memory;     //3:设置instance指向刚分配的内存地址  
  3.                        //注意,此时对象还没有被初始化!  
  4. ctorInstance(memory);  //2:初始化对象  
memory = allocate();   //1:分配对象的内存空间instance = memory;     //3:设置instance指向刚分配的内存地址                       //注意,此时对象还没有被初始化!ctorInstance(memory);  //2:初始化对象
将第2步和第3步调换顺序,在单线程情况下不会影响程序执行的结果,但是在多线程情况下就不一样了。线程A执行了instance = memory(这对另一个线程B来说是可见的),此时线程B执行外层 if (instance == null),发现instance不为空,随即返回,但是 得到的却是未被完全初始化的实例,在使用的时候必定会有风险,这正是双重检查锁定的问题所在!

鉴于DCL的缺陷,便有了修订版:

[java]
  1. public static Singleton getInstance() {  
  2.         if (instance == null) {  
  3.             synchronized (Singleton.class) {  
  4.                 Singleton temp = instance;  
  5.                 if (temp == null) {  
  6.                     synchronized (Singleton.class) {  
  7.                         temp = new Singleton();  
  8.                     }  
  9.                     instance = temp;  
  10.                 }  
  11.             }  
  12.         }  
  13.         return instance;  
  14.     }  
public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                Singleton temp = instance;                if (temp == null) {                    synchronized (Singleton.class) {                        temp = new Singleton();                    }                    instance = temp;                }            }        }        return instance;    }
修订版试图引进局部变量和第二个synchronized来解决指令重排序的问题。但是,Java语言规范虽然规定了同步代码块内的代码必须在对象锁释放之前执行完毕,却没有规定同步代码块之外的代码不能在对象锁释放之前执行,也就是说 instance = temp 可能会在编译期或者运行期移到里层的synchronized内,于是又会引发跟DCL一样的问题。

在JDK1.5之后,可以使用volatile变量禁止指令重排序,让DCL生效:

[java]
  1. package com.zzj.pattern.singleton;  
  2.   
  3. public class Singleton {  
  4.     private static volatile Singleton instance;  
  5.   
  6.     private Singleton() {  
  7.     }  
  8.   
  9.     public static Singleton getInstance() {  
  10.         if (instance == null) {  
  11.             synchronized (Singleton.class) {  
  12.                 if (instance == null) {  
  13.                     instance = new Singleton();  
  14.                 }  
  15.             }  
  16.         }  
  17.         return instance;  
  18.     }  
  19. }  
package com.zzj.pattern.singleton;public class Singleton {    private static volatile Singleton instance;    private Singleton() {    }    public static Singleton getInstance() {        if (instance == null) {            synchronized (Singleton.class) {                if (instance == null) {                    instance = new Singleton();                }            }        }        return instance;    }}
volatile的另一个语义是保证变量修改的可见性。

单例模式还有如下实现方式:

[java]
  1. package com.zzj.pattern.singleton;  
  2.   
  3. public class Singleton {  
  4.     private static class InstanceHolder {  
  5.         public static Singleton instance = new Singleton();  
  6.     }  
  7.   
  8.     private Singleton() {  
  9.     }  
  10.   
  11.     public static Singleton getInstance() {  
  12.         return InstanceHolder.instance;  
  13.     }  
  14. }  
package com.zzj.pattern.singleton;public class Singleton {    private static class InstanceHolder {        public static Singleton instance = new Singleton();    }    private Singleton() {    }    public static Singleton getInstance() {        return InstanceHolder.instance;    }}
这种方式称为 延迟初始化占位(Holder)类模式。该模式引进了一个静态内部类(占位类),在内部类中提前初始化实例,既保证了Singleton实例的延迟初始化,又保证了同步。这是一种提前初始化(恶汉式)和延迟初始化(懒汉式)的综合模式。

至此,正确的单例模式有三种实现方式:

1.提前初始化。

[java]
  1. package com.zzj.pattern.singleton;  
  2.   
  3. public class Singleton {  
  4.     private static Singleton instance = new Singleton();  
  5.   
  6.     private Singleton() {  
  7.     }  
  8.   
  9.     public static Singleton getInstance() {  
  10.         return instance;  
  11.     }  
  12. }  
package com.zzj.pattern.singleton;public class Singleton {    private static Singleton instance = new Singleton();    private Singleton() {    }    public static Singleton getInstance() {        return instance;    }}
2.双重检查锁定 + volatile。

3.延迟初始化占位类模式。

转载地址:http://gjcrj.baihongyu.com/

你可能感兴趣的文章
Dubbo基础知识整理
查看>>
计算机网络知识整理
查看>>
Java基础知识
查看>>
操作系统知识整理
查看>>
实现自己的权限管理系统(二):环境配置以及遇到的坑
查看>>
实现自己的权限管理系统(四): 异常处理
查看>>
实现自己的权限管理系统(十):角色模块
查看>>
实现自己的权限管理系统(十二):权限操作记录
查看>>
实现自己的权限管理系统(十三):redis做缓存
查看>>
实现自己的权限管理系统(十四):工具类
查看>>
JavaWeb面经(二):2019.9.16 Synchronized关键字底层原理及作用
查看>>
牛客的AI模拟面试(1)
查看>>
深入浅出MyBatis:MyBatis解析和运行原理
查看>>
Mybatis与Ibatis
查看>>
字节码文件(Class文件)
查看>>
java中的IO流(一)----概述
查看>>
StringBuilder
查看>>
集合,Collection
查看>>
泛型详解
查看>>
泛型实现斗地主
查看>>