单例模式Double-Checked

本文深入探讨了使用Double-Checked Locking(DCL)和Volatile关键字实现单例模式的方法,即双重检查锁定模式。文章详细解释了DCL单例模式的实现步骤,包括构造器私有化、提供私有的静态属性以及公共的静态方法来获取属性。同时,文中也讨论了指令重排序可能带来的问题以及如何通过使用volatile关键字来避免这些问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这里主要用Double-Checked和Volatile实现单例模式 (双重检查锁定模式)

DCL单例模式: 懒汉式套路基础上加入并发控制,保证在多线程环境下,对外存在一个对象
  1、构造器私有化 -->避免外部new构造器
  2、提供私有的静态属性 -->存储对象的地址
  3、提供公共的静态方法 --> 获取属性

public class DoubleCheckedLocking {
    //2、提供私有的静态属性
    //没有volatile其他线程可能访问一个没有初始化的对象
    private static volatile DoubleCheckedLocking instance;
    //1、构造器私有化
    private DoubleCheckedLocking() {
    }
    
    //3、提供公共的静态方法 --> 获取属性
    public static DoubleCheckedLocking getInstance() {
        //再次检测
        if(null!=instance) { //避免不必要的同步 ,已经存在对象
            return instance;
        }
        synchronized(DoubleCheckedLocking.class) {
            if(null == instance) {
                instance = new DoubleCheckedLocking();
                //1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
            }
        }
        return instance;
    }
    public static DoubleCheckedLocking getInstance1(long time) {
        if(null == instance) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new DoubleCheckedLocking();
            //1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
        }
        return instance;
    }
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println(DoubleCheckedLocking.getInstance());
        }) ;
        t.start();
        System.out.println(DoubleCheckedLocking.getInstance());
    }

}

这种方式称为延迟初始化,但是在多线程的情况下会失效,于是使用同步锁,给getInstance() 方法加锁:

public static synchronized DoubleCheckedLocking getInstance() {
    if (instance == null) {
        instance = new DoubleCheckedLocking();
    }
    return instance;
}

同步是需要开销的,我们只需要在初始化的时候同步,而正常的代码执行路径不需要同步,这里加入双重检查加锁(DCL)

public static DoubleCheckedLocking getInstance() {
    //再次检测
    if (null != instance) { //避免不必要的同步 ,已经存在对象
        return instance;
    }
    synchronized (DoubleCheckedLocking.class) {
        if (null == instance) {
            instance = new DoubleCheckedLocking();
            //1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
        }
    }
    return instance;
}

这样一种设计可以保证只产生一个实例,并且只会在初始化的时候加同步锁,看似精妙绝伦,但却会引发另一个问题,这个问题由指令重排序引起。指令重排序是为了优化指令,提高程序运行效率。指令重排序包括编译器重排序和运行时重排序。JVM规范规定,指令重排序可以在不影响单线程程序执行结果前提下进行。

如何避免呢?加入volatile

在我们new一个对象的时候会做三件事情,1、开辟空间 2、初始化对象信息 3、返回对象的地址给引用,如果有构造器在哪初始化得过程中或者耗时较慢的情况下,就有可能先于第2步将对象的地址值给了引用,造成结果可能会遇到这种情况:A线程正在初始化这个对象,这时候B线程已经拿到了这个引用,B线程拿出的对象值就有可能是个空的对象

如何避免指令重排呢?

在JDK1.5之后,可以使用volatile变量禁止指令重排序,让DCL生效:


如果没有和同步,会存在不一致的情况

 public static DoubleCheckedLocking getInstance1(long time) {
        if(null == instance) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            instance = new DoubleCheckedLocking();
            //1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
        }
        return instance;
    }
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println(DoubleCheckedLocking.getInstance1(500));
        }) ;
        t.start();
        System.out.println(DoubleCheckedLocking.getInstance1(1000));
    }

}

如果加入同步,结果一样

//3、提供公共的静态方法 --> 获取属性
    public static DoubleCheckedLocking getInstance(long time) {
        //再次检测
        if(null!=instance) { //避免不必要的同步 ,已经存在对象
            return instance;
        }
        synchronized(DoubleCheckedLocking.class) {
            if(null == instance) {
                instance = new DoubleCheckedLocking();
                //1、开辟空间 //2、初始化对象信息 //3、返回对象的地址给引用
            }
        }
        return instance;
    }
    
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println(DoubleCheckedLocking.getInstance(500));
        }) ;
        t.start();
        System.out.println(DoubleCheckedLocking.getInstance(1000));
    }

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值