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