public class DoubleCheckedLocking{
private static Instance instance;
public static Instance getInstance(){
if( instance == null ){
synchronized (DoubleCheckedLocking.class){
if( instance == null){
instance = new Instance();
}
}
}
return instance;
}
}
这种双重锁单例应该是已经很完美,但是关键在于 new Instance()这句代码,在底层分为 allocation;init;return;即内存分配,初始化,返回内存地址。而在底层会进行重排,重排为内存分配,返回内存地址,初始化(这种重排不违反 intra-thread semantices,能保证的是在同一线程内,初始化 happens-before 使用)。这样会导致 A线程进入后,内存分配,返回内存地址后,在初始化前,B 线程进入,发现 instance 不为 null,则返回使用。此时 instance 还为进行初始化,因此导致的线程不安全。
能完美解决的单例是用用 volatile 禁止初始化和返回的重排或静态内部类单例
public class DoubleCheckedLocking{
private volatile static Instance instance;
public static Instance getInstance(){
if( instance == null ){
synchronized (DoubleCheckedLocking.class){
if( instance == null){
instance = new Instance();
}
}
}
return instance;
}
}
在多线程环境,volatile 会禁止初始化和返回的重排。
public class SingletonIniti {
private SingletonIniti() {
}
private static class SingletonHolder {
private static final SingletonIniti INSTANCE = newSingletonIniti();
}
public static SingletonIniti getInstance() {
return SingletonHolder.INSTANCE;
}
}
因为类的加载和类的初始化在 jvm 内部是由 jvm 保证的线程安全。不允许多个线程同时加载同一个类。同时可能会重排,但是禁止非构造线程看到这种重排。即整个实例化对于非构造线程是透明的。整个实例化作为原子操作在构造线程中。
网友评论