单例模式在实际工作或面试中出现的频率更高,double-checked lock是单例常见实现:
public class SimpleSingleton4 {
private static SimpleSingleton4 INSTANCE;
private SimpleSingleton4() { }
public static SimpleSingleton4 getInstance() { if (INSTANCE == null) { synchronized (SimpleSingleton4.class) { if (INSTANCE == null) { INSTANCE = new SimpleSingleton4(); } } } return INSTANCE; } }
|
这段代码有问题:
public static SimpleSingleton4 getInstance() { if (INSTANCE == null) {//1 synchronized (SimpleSingleton4.class) {//2 if (INSTANCE == null) {//3 INSTANCE = new SimpleSingleton4();//4 } } } return INSTANCE;//5 }
|
你希望按1, 2, 3, 4, 金额 5顺序执行。但是Java虚拟机实际上会做一些优化,重新排列一些代码指令。重排后的顺序可能会变成: 1, 3, 2, 4, 5,这样在多线程的情况下也会创建多个实例。重新排列的代码可能如下所示:
public static SimpleSingleton4 getInstance() { if (INSTANCE == null) {//1 if (INSTANCE == null) {//3 synchronized (SimpleSingleton4.class) {//2 INSTANCE = new SimpleSingleton4();//4 } } } return INSTANCE;//5 }
|
有什么解决办法?
答:您可以将volatile关键字添加到 的定义中INSTANCE。具体代码如下:
public class SimpleSingleton7 {
private volatile static SimpleSingleton7 INSTANCE;
private SimpleSingleton7() { }
public static SimpleSingleton7 getInstance() { if (INSTANCE == null) { synchronized (SimpleSingleton7.class) { if (INSTANCE == null) { INSTANCE = new SimpleSingleton7(); } } } return INSTANCE; } }
|
关键字可以保证多线程的volatile可见性但不保证原子性,也可以禁止指令重排序。
双重检查锁的机制不仅保证了线程安全,而且相比直接锁,提高了执行效率,节省了内存空间。
volatile是一个非常好的关键字,它可以保证变量在多线程中的可见性,也可以禁止指令重排,但不能保证原子性。
可见性主要体现在:一个线程修改一个变量,另一个线程每次都可以得到该变量的最新值。
使用synchronized关键字保证原子性:
public class VolatileTest {
public int count = 0; //这里使用
|
volatile没有用,无法保证原子性
//public volatile int count = 0;
public synchronized void add() { count++; }
public static void main(String[] args) { final VolatileTest test = new VolatileTest(); for (int i = 0; i < 20; i++) { new Thread() { @Override public void run() { for (int j = 0; j < 1000; j++) { test.add(); } }
; }.start(); } while (Thread.activeCount() > 2) { //Ensure that all previous threads are executed Thread.yield(); }
System.out.println(test.count); } }
|