Java:双重检查锁定模式

Java:双重检查锁定模式

2020-02-23
tips
java

首先验证锁定条件(第一次检查),只有通过锁定条件验证才真正的进行加锁逻辑并再次验证条件(第二次检查)

用来减少并发系统中竞争和同步的开销

场景 1

class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            helper = new Helper();
        }
        return helper;
    }

    ......
}

单线程,按顺序执行,没有任何问题

多线程并发时:if (helper == null)同时满足时,不同线程都会执行new Helper()创建新的Helper实例,可能会导致某个线程获取到的实例不完整

解决办法:加锁synchronized

场景 2

class Foo {
    private Helper helper = null;
    public synchronized Helper getHelper() {
        if (helper == null) {
            helper = new Helper();
        }
        return helper;
    }

    ......
}

多线程并发,加锁后按顺序执行,没有任何问题

问题:每次访问都获取和释放锁,资源开销大,影响性能

解决办法:先检查实例初始化情况,如果已初始化则直接返回实例,如果没有初始化再进入锁环节

场景 3

class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }

    ......
}

根据Java内存模型, 每个线程有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存

问题:可能会存在线程1初始化了Helper实例,但是线程2并会及时获取到实例信息,导致再进入锁等待环节

解决办法:volatile关键字,被volatile修饰的变量能够保证每个线程能够获取该变量的最新值,从而避免出现数据脏读的现象

场景 4

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;
    }

    ......
}

局部变量result的使用看起来是不必要的。对于某些版本的Java虚拟机,这会使代码提速25%,而对其他的版本则无关痛痒

参考: