这里主要用Double-Checked和Volatile实现单例模式 (双重检查锁定模式)
DCL单例模式: 懒汉式套路基础上加入并发控制,保证在多线程环境下,对外存在一个对象
1、构造器私有化 -->避免外部new构造器
2、提供私有的静态属性 -->存储对象的地址
3、提供公共的静态方法 --> 获取属性
public class DoubleCheckedLocking {
//2、提供私有的静态属性
//没有volatile其他线程可能访问一个没有初始化的对象
private static volatile DoubleCheckedLocking instance;
//1、构造器私有化
private DoubleCheckedLocking() {
}
//3、提供公共的静态方法 --> 获取属性
public static DoubleCheckedLocking getInstance() {
//再次检测
if(null!=instance) { //避免不必要的同步 ,已经存在对象
return instance;
}
synchronized(DoubleCheckedLocking.class) {
if(null == instance) {
instance = new DoubleCheckedLocking();
//1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
}
}
return instance;
}
public static DoubleCheckedLocking getInstance1(long time) {
if(null == instance) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new DoubleCheckedLocking();
//1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
}
return instance;
}
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println(DoubleCheckedLocking.getInstance());
}) ;
t.start();
System.out.println(DoubleCheckedLocking.getInstance());
}
}
这种方式称为延迟初始化,但是在多线程的情况下会失效,于是使用同步锁,给getInstance() 方法加锁:
public static synchronized DoubleCheckedLocking getInstance() {
if (instance == null) {
instance = new DoubleCheckedLocking();
}
return instance;
}
同步是需要开销的,我们只需要在初始化的时候同步,而正常的代码执行路径不需要同步,这里加入双重检查加锁(DCL)
public static DoubleCheckedLocking getInstance() {
//再次检测
if (null != instance) { //避免不必要的同步 ,已经存在对象
return instance;
}
synchronized (DoubleCheckedLocking.class) {
if (null == instance) {
instance = new DoubleCheckedLocking();
//1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
}
}
return instance;
}
这样一种设计可以保证只产生一个实例,并且只会在初始化的时候加同步锁,看似精妙绝伦,但却会引发另一个问题,这个问题由指令重排序引起。指令重排序是为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。JVM规范规定,指令重排序可以在不影响单线程程序执行结果前提下进行。
如何避免呢?加入volatile
在我们new一个对象的时候会做三件事情,1、开辟空间 2、初始化对象信息 3、返回对象的地址给引用,如果有构造器在哪初始化得过程中或者耗时较慢的情况下,就有可能先于第2步将对象的地址值给了引用,造成结果可能会遇到这种情况:A线程正在初始化这个对象,这时候B线程已经拿到了这个引用,B线程拿出的对象值就有可能是个空的对象
如何避免指令重排呢?
在JDK1.5之后,可以使用volatile变量禁止指令重排序,让DCL生效:
如果没有和同步,会存在不一致的情况
public static DoubleCheckedLocking getInstance1(long time) {
if(null == instance) {
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new DoubleCheckedLocking();
//1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
}
return instance;
}
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println(DoubleCheckedLocking.getInstance1(500));
}) ;
t.start();
System.out.println(DoubleCheckedLocking.getInstance1(1000));
}
}
如果加入同步,结果一样
//3、提供公共的静态方法 --> 获取属性
public static DoubleCheckedLocking getInstance(long time) {
//再次检测
if(null!=instance) { //避免不必要的同步 ,已经存在对象
return instance;
}
synchronized(DoubleCheckedLocking.class) {
if(null == instance) {
instance = new DoubleCheckedLocking();
//1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
}
}
return instance;
}
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println(DoubleCheckedLocking.getInstance(500));
}) ;
t.start();
System.out.println(DoubleCheckedLocking.getInstance(1000));
}
}