乐趣区

关于java:单例模式之双重校验锁的优缺点

单例设计双重校验锁这种形式采纳双锁机制,平安且在多线程状况下能放弃高性能。但其中也有优缺点

双重校验锁代码

public class DoubleLock {

private static DoubleLock doubleLock;

private DoubleLock(){

}

public static DoubleLock getInstance(){
if (doubleLock == null){
synchronized (DoubleLock.class){
if (doubleLock == null){
doubleLock = new DoubleLock();
}
}
}
return doubleLock;
}
}

长处

平安且在多线程状况下能放弃高性能,第一个 if 判断防止了其余无用线程竞争锁来造成性能节约,第二个 if 判断能拦挡除第一个取得对象锁线程以外的线程。

如果不加第二次判空,咱们思考下线程 A,线程 B 都阻塞在了获取锁的步骤上,其中 A 取得锁 — 实例化了对象 —- 开释锁,之后 B — 取得锁 — 实例化对象,此时违反了咱们单例模式的初衷。

问题

双重查看锁定背地的实践是完满的。可怜地是,事实齐全不同。双重查看锁定的问题是:并不能保障它会在单处理器或多处理器计算机上顺利运行。

双重查看锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型容许所谓的“无序写入”,这也是这些习语失败的一个次要起因。

singleton = new Singleton();

该语句非原子操作,理论是三个步骤。

  • 1. 给 Singleton 分配内存;
  • 2. 调用 Singleton 的构造函数来初始化成员变量;
  • 3. 将给 singleton 对象指向调配的内存空间(此时 singleton 才不为 null);

虚拟机的 指令重排序–>

执行命令时虚拟机可能会对以上 3 个步骤替换地位 最初可能是 132 这种 分配内存并批改指针后未初始化 多线程获取时可能会呈现问题。

线程 A 进入同步办法执行 singleton = new Singleton(); 代码时,恰好这三个步骤重排序后为1 3 2

那么 步骤 3 执行后 singleton 曾经不为 null , 然而未执行 步骤 2 singleton对象初始化不齐全,此时 线程 B 执行 getInstance() 办法,第一步判断时 singleton 不为 null, 则间接将未齐全初始化的 singleton 对象返回了。

解决

如果一个字段被申明成 volatile,Java 线程内存模型确保所有线程看到这个变量的值是统一的,同时还会禁止指令重排序

所以应用 volatile 关键字会禁止指令重排序, 能够防止这种问题。应用 volatile 关键字后使得 singleton = new Singleton();语句肯定会依照下面拆分的步骤 123 来执行。

另一个问题

单例模式并不是相对平安的,能够通过反射来毁坏,只有枚举安全类是平安的。

局部内容参考链接:https://blog.csdn.net/qq646040754/article/details/81327933

退出移动版