线程安全的延迟初始化的实现

几个延迟初始化的例子,其中包含非线程安全的和线程安全的,自己可以现在心里判断一下哪些是安全的哪些是不安全的。

下面是用例类:

class Instance {}
class UnsafeLazyInit {
    private static Instance instance;

    public static Instance getInstance() {
        if (instance == null) {
            instance = new Instance();
        }
        return instance;
    }
}
class SyncMethodLazyInit {
    private static Instance instance;

    public synchronized static Instance getInstance() {
        if (instance == null) {
            instance = new Instance();
        }
        return instance;
    }
}
class SyncSegmentLazyInit {
    private static Instance instance;

    public static Instance getInstance() {
        if (instance == null) {  // #1
            synchronized (SyncSegmentLazyInit.class) {  // #2
                instance = new Instance();  // #3
            }
        }
        return instance;
    }
}
class SyncSegmentAndCheckLazyInit {
    private static Instance instance;

    public static Instance getInstance() {
        if (instance == null) {  // #1
            synchronized (SyncSegmentAndCheckLazyInit.class) {  // #2
                if (instance == null) {  // #3
                    instance = new Instance();  // #4
                }
            }  // #5
        }
        return instance;  // #6
    }
}
class SyncSegmentWithVolatileLazyInit {
    private static volatile Instance instance;

    public static Instance getInstance() {
        if (instance == null) {
            synchronized (SyncSegmentWithVolatileLazyInit.class) {
                if (instance == null) {
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}
class ClassLockLazyInit {
    private static class InstanceHolder {
        static Instance instance = new Instance();
    }

    public static Instance getInstance() {
        return InstanceHolder.instance;
    }
}

上述类的分析:

首先说一下答案,UnsafeLazyInit,SyncSegmentLazyInit,SyncSegmentAndCheckLayInit这三个类对于instance对象的延迟初始化是线程不安全的,剩下的几个是安全的。

UnsafeLazyInit类对于instance对象的初始化没有任务防护措施,其非安全性显而易见。SyncSegmentLazyInit类的getInstance方法,在步骤#1执行之后,#2执行之前,可能会被其他线程抢占CPU并完成对instance的初始化,当该线程再次获得CPU之后,开始执行#2,这时候instance已经被初始化过了,执行#3意味着对instance的重新赋值。这时候大家可能会产生这样的疑问:SyncSegmentAndCheckLayInit在加锁之后,又检验了一遍instance是否为null,为什么依然是线程不安全的呢?原因就在于第#4步。实际上#4这一步分为三步完成:

memory = allocate(); // 1.分配对象的内存空间
constructInstance(memory);  // 2.初始化对象
instance = memory;  //3.设置instance指向分配的内存地址

上面三行伪代码中的2和3之间,可能会被重排序(即使重排序,也一定保证步骤2在#6之前完成)。假设A线程在执行完SyncSegmentAndCheckLayInit的getInstance方法的#4后释放了锁,在首次访问instance也就是return之前,被B线程抢占CPU,这时候B线程判断instance为null将会返回false,B线程将会访问未被正确初始化的对象instance。

解决上述问题的办法就是用volatile域修饰instance,volatile关键字将禁止CPU对其所修饰的域的操作进行重排序。

除了使用volatile域修饰并进行双重判断之外,还可以将instance作为内部类的静态变量,来实现线程安全的延迟初始化。因为JVM在初始化类之前会先获取一个锁,保证同一时刻只有一个线程执行类的初始化操作,在初始化类的时候就会初始化类的静态成员。那么何时会初始化一个类呢?在首次发生下面任意一种情况时:

 

以上就是延迟初始化的几个例子,正确的延迟初始化实现了也是实现了单例模式,然而单例模式也有非延迟初始化的实现,比如依赖enum的实现,有兴趣的同学可以自己试一下,想一下其中的原理。

  • T是一个类,一个T类型的实例被创建
  • T是一个类,且T汇总的一个静态方法被调用
  • T中的一个静态字段被赋值
  • T中的一个静态字段被访问,且这个字段不是常量字段
  • T是一个顶级类,且类中有断言

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值