实现单例模式
public class SingletonDemo {
/**
* 禁止指令重排volatile
*/
private static volatile SingletonDemo instance = null;
// private static SingletonDemo instance=null;
private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + "\t 我是构造方法SingleDemo()");
}
/**
* DCL(double check lock双端检锁机制)
*/
public static SingletonDemo getInstance() {
if (instance == null) {
synchronized (SingletonDemo.class) {
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, String.valueOf(i)).start();
}
}
}
运行结果
0 我是构造方法SingleDemo()
- 在上⾯的代码中,我们⾸先判断 instance 是否为空,如果不为空直接返回。如果同时有多个线程都发现
instance ==null
为空的话,就会去创建这个对象,但是创建部分的代码块使⽤了 synchronized 关键字加锁,这样就保证了某⼀时刻只能有⼀个线程可以执⾏创建对象这部分代码块,也就保证了当前系统只存在⼀个 SingletonDemo 对象。 - 并且因为
instance = new SingletonDemo();
不是原⼦操作,所以这段代码可以
简单分为下⾯三步执⾏:1.为 instance 分配内存空间;2.初始化 instance ;3.将 instance 指向分配的内存地址。 - 由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1->3->2。指令重排在单线程环境
下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。例如,线程 a执⾏了 1 和 3,此时 线程 b 调⽤ getInstance() 后发现 instance 不为空,因此返回instance ,但此时 instance 还未被初始化,所以就会导致空指针异常。 - 而使⽤ volatile 修饰变量就可以禁⽌ JVM 的指令重排,保证在多线程环境下也能正常运⾏。所以需要使用volatile修饰对象。
private static volatile SingletonDemo instance = null;