单例模式中的双重校验锁

// 单线程的时候
class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            helper = new Helper();
        }
        return helper;
    }

}

这段在使用多线程的情况下无法正常工作。在多个线程同时调用getHelper()时,必须要获取,否则,这些线程可能同时去创建对象,或者某个线程会得到一个未完全初始化的对象。

锁可以通过代价很高的同步来获得,就像下面的例子一样。

//这样写虽然正确,但是粗暴地把getHelper锁住了,这样代价很大
class Foo {
    private Helper helper = null;
    public synchronized Helper getHelper() {
        if (helper == null) {
            helper = new Helper();
        }
        return helper;
    }

}

只有getHelper()的第一次调用需要同步创建对象,创建之后getHelper()只是简单的返回成员变量,而这里是无需同步的。 由于同步一个方法会降低100倍或更高的性能[2], 每次调用获取和释放锁的开销似乎是可以避免的:一旦初始化完成,获取和释放锁就显得很不必要。许多程序员一下面这种方式进行优化:

  1. 检查变量是否被初始化(不去获得锁),如果已被初始化立即返回这个变量。
  2. 获取锁
  3. 第二次检查变量是否已经被初始化:如果其他线程曾获取过锁,那么变量已被初始化,返回初始化的变量。
  4. 否则,初始化并返回变量。
//这种双重检查锁定就很好地解决问题,避免每次调用都要获取锁
class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null) {
            synchronized(this) {
                if (helper == null) {
                    helper = new Helper();
                }
            }
        }
        return helper;
    }

}

 

posted @ 2017-08-07 16:22  写代码隔扣邓肯  阅读(6082)  评论(1编辑  收藏  举报