为什么在Java中设计单例模式时需要双重检查锁?
Why we need double check lock while designing singleton pattern in Java?
为什么我们需要在获取锁之前和之后检查null?
有一次,我们获得了锁,没有线程可以拥有锁那么为什么在同步块之前不需要空检查?
public class DclSingleton {
private static volatile DclSingleton instance;
public static DclSingleton getInstance() {
**if (instance == null) {**
synchronized (DclSingleton .class) {
**if (instance == null) {**
instance = new DclSingleton();
}
}
}
return instance;
}
// private constructor and other methods...
}
想象下一个场景:
- 线程 1 检查
instance == null
并发现此条件为真。
- 线程 2 检查
instance == null
并发现此条件为真。
- 线程 1 获取锁。
- 线程 2 尝试获取锁,它已经获取,因此线程 2 等待。
- 线程 1 初始化
instance = new DclSingleton()
。
- 线程1释放锁。
- 线程 2 获取锁。
- 线程 2 初始化
instance = new DclSingleton()
。 我们有双重初始化。
您检查 null
两次,因为:
- 如果在
DclSingleton.class
上同步之前不检查,那么每次调用都会同步,这可能会很慢(想象一下非常频繁地使用单例实例)。
- 如果不检查
null
inside synchronized
块,多个线程有可能在没有机会的情况下进行第一次检查锁定对象,您将重新创建实例。
要了解为什么需要双重 null
检查,请查看已经给出的答案。
另一种安全初始化单例实例的方法是static holder pattern,它的实现方式如下:
public class DclSingleton {
public static DclSingleton getInstance() {
return Holder.INSTANCE;
}
private static class Holder {
private static final DclSingleton INSTANCE = new DclSingleton();
}
}
JVM已经以线程安全的方式初始化了classes,所以即使2个线程同时访问getInstance()
,JVM也会初始化Holder
class 只有一次,因此你有正确的初始化。
另外Holder
class会延迟加载,所以只有在第一次被引用时才会初始化。例如,当 getInstance()
是第一次被调用时
为什么我们需要在获取锁之前和之后检查null? 有一次,我们获得了锁,没有线程可以拥有锁那么为什么在同步块之前不需要空检查?
public class DclSingleton {
private static volatile DclSingleton instance;
public static DclSingleton getInstance() {
**if (instance == null) {**
synchronized (DclSingleton .class) {
**if (instance == null) {**
instance = new DclSingleton();
}
}
}
return instance;
}
// private constructor and other methods...
}
想象下一个场景:
- 线程 1 检查
instance == null
并发现此条件为真。 - 线程 2 检查
instance == null
并发现此条件为真。 - 线程 1 获取锁。
- 线程 2 尝试获取锁,它已经获取,因此线程 2 等待。
- 线程 1 初始化
instance = new DclSingleton()
。 - 线程1释放锁。
- 线程 2 获取锁。
- 线程 2 初始化
instance = new DclSingleton()
。 我们有双重初始化。
您检查 null
两次,因为:
- 如果在
DclSingleton.class
上同步之前不检查,那么每次调用都会同步,这可能会很慢(想象一下非常频繁地使用单例实例)。 - 如果不检查
null
insidesynchronized
块,多个线程有可能在没有机会的情况下进行第一次检查锁定对象,您将重新创建实例。
要了解为什么需要双重 null
检查,请查看已经给出的答案。
另一种安全初始化单例实例的方法是static holder pattern,它的实现方式如下:
public class DclSingleton {
public static DclSingleton getInstance() {
return Holder.INSTANCE;
}
private static class Holder {
private static final DclSingleton INSTANCE = new DclSingleton();
}
}
JVM已经以线程安全的方式初始化了classes,所以即使2个线程同时访问getInstance()
,JVM也会初始化Holder
class 只有一次,因此你有正确的初始化。
另外Holder
class会延迟加载,所以只有在第一次被引用时才会初始化。例如,当 getInstance()
是第一次被调用时