Java并发编程中双重检查锁漏洞


单例模式在实际工作或面试中出现的频率更高,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);
    }
}