java双重检查锁定模式 双if判断
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getInstance() {
//5.此时线程T4进来,直接return对象
//1. 假设T1,T2,T3 三个线程进入,都通过第一层if
if (uniqueInstance == null) {
//2.因为加了锁,此时只能进入一个,假设T2进入,T1、T3等待
synchronized (Singleton.class) {
//3.T2通过判断,成功返回
//4.此时T1进入,发现pojo有值,不再new直接return前面创建好的pojo,T3类推
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
// 这里的uniqueInstance为什么要使用volatile修饰???
/**
【维基百科解释】:
线程T2发现变量没有被初始化, 然后它获取锁并开始变量的初始化。
由于某些编程语言的语义,编译器生成的代码允许在线程T2执行完变量的初始化之前,更新变量并将其指向部分初始化的对象。
线程T1或者T3发现共享变量已经被初始化,并返回变量。由于线程B确信变量已被初始化,它没有获取锁。如果在A完成初始化之前共享变量对B可见(这是由于A没有完成初始化或者因为一些初始化的值还没有覆盖B使用的内存(缓存一致性)),程序很可能会崩溃。
*/
//================== 下面使用小段代码再次说明 ==================
/**
volatile 关键字的作用是保证变量在多线程之间的可见性,
即每次读取该变量的值时都会强制从主内存中读取。
下面是一个使用 volatile 关键字和不使用的例子:
*/
public class VolatileTest {
private static volatile boolean flag = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (!flag) {
}
System.out.println("Thread1 finished.");
}).start();
Thread.sleep(1000);
new Thread(() -> {
flag = true;
System.out.println("Thread2 finished.");
}).start();
}
}
/**
在上面的例子中,如果不使用 volatile 关键字,那么线程1将永远无法结束,
因为它无法感知到线程2对变量的修改。
但是,如果使用了 volatile 关键字,那么线程1将会感知到线程2对变量的修改并结束1
*/
//这段代码中,有兴趣的朋友也可以测试一下
//局部变量result的使用看起来是不必要的。对于某些版本的Java虚拟机,这会使代码提速25%,
//而对其他的版本则无关痛痒。
class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
Helper result = helper;
if (result == null) {
synchronized(this) {
result = helper;
if (result == null) {
helper = result = new Helper();
}
}
}
return result;
}
}
//如果说获取的对象为静态修饰,还可以使用【惰性初始化模式】来替代,有兴趣可以自行上网搜索
//final语义可以不使用volatile关键字实现安全的创建对象 有兴趣可以自行上网搜索
//最后,类似单例模式,或者一些对应的业务,需要我们保证在并发时只生产一份的情况下,我们可以使用该方式。