DCL(双检查锁)实现单例模式--饿汉模式和懒汉模式

单例模式是设计模式中比较简单的一种。在编程开发中主要解决的问题就是在一个系统中,需要一个单例对象被一个系统的不同模块的不同对象所访问,且保证访问的都是同一个对象,因此便需要一个全局的访问指针,这便是众所周知的单例模式的应用。但是如果编写出一个线程安全且高效的单例模式,却需要考虑很多很多问题,接下来我们就一步一步的分析。

 1、立即加载/“饿汉模式”

  什么是立即加载?立即加载就是使用类的时候已经将对象创建完毕,常见的实现方法就是直接new实例化。而立即加载从中文的语境上看,有“着急”、“急迫”的含义,所以也称为“饿汉模式”

  立即加载/“饿汉模式”是在调用方法前,实例已被创建了,来看一下示例代码

创建单例类

public class MyObject {
    //立即加载==饿汉模式
    private static MyObject myObject = new MyObject();
    private MyObject(){

    }
    public static MyObject getInstance(){
        return myObject;
    }
}

 创建线程类

public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(MyObject.getInstance().hashCode());
    }
}

 创建测试类

public class Run {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果

215722165
215722165
215722165

Process finished with exit code 0

我们已经创建了一个普通的单例模式的类,通过控制台的打印看到hashcode是同一个值,说明对象是同一个,也就完成我们刚刚所要求的饿汉模式。这个类很简单,代码也很容易理解,因为单例静态实例对象在编译的时候就已经完成了初始化,所以不存在线程安全问题。

2、延迟加载/“懒汉模式”

  什么是延迟加载?延迟加载就是在调用get()方法时实例才被创建,常见的实现方法就是在get()方法的时候实例化。而延迟加载从中文的语境来看,是“缓慢”、“不急迫”的含义,所以也称为“懒汉模式”

  1.延迟加载/“懒汉模式”解析

  延迟加载/“懒汉模式”是在调用方法时实例才被创建,让我们看一下实现代码。我们只需要改变MyObject的代码即可

public class MyObject {
    private static MyObject myObject;
    private MyObject(){

    }
    public static MyObject getInstance(){
        //延迟加载
        if(myObject != null){

        }else {
            myObject = new MyObject();
        }
        return myObject;
    }
}

这里建议大家动手去敲一下代码,因为代码比较简单,就直接说结果了。

如果是单个线程运行的话,取的对象肯定总是同一个对象,但是在多线程环境中,有时候取的对象就有可能不是同一个对象,这个时候就出现了线程安全问题,请大家耐心的往后面去看,后面有详细的解决方案。

 

总结:虽然懒汉模式实现了单例设计模式,但是在多线程的环境下存在线程安全问题,根本就不可能保证多线程环境下取得的是同一个对象。

接下来我们看看如何在多线程环境下如果较为安全的单例模式。

我们还是设计刚刚的那个懒汉模式,只需要修改MyObject代码即可

public class MyObject {
    private static MyObject myObject;
    private MyObject(){

    }
    synchronized public static MyObject getInstance(){
        try{
            if(myObject!=null){

            }else{
                //模拟在创建对象之前做一些准备工作
                Thread.sleep(3000);
                myObject = new MyObject();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return myObject;
    }
}

结果:通过控制台运行结果,我们可以看到,我们在多线程的环境下实现了单例模式,但是通过synchronized加锁的这种方式去把整个方法锁起来,只有线程拿到锁,并释放资源,这个时候线程还得去抢夺锁,抢到了才能获取单例模式类,进行下一步操作。 

针对上面synchronized锁的粒度比较大,这次我们把比较重要的一些代码段锁起来。

继续改写MyObject代码,其余的代码保持不变


public class MyObject {
    private static MyObject myObject;
    private MyObject(){

    }
    public static MyObject getInstance(){
        try{
            if(myObject!=null){

            }else{
                //模拟在创建对象之前做一些准备工作
                Thread.sleep(3000);
                synchronized (MyObject.class){
                    myObject = new MyObject();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return myObject;
    }
}

 

 此方法使用synchronized同步代码块,只对实例化的关键对象进行了同步,从语句的结构上说,运行的效率确实得到了提升,但是如果在多线程环境下,还是无法解决线程安全问题,我们接下来推出最终解决方案。

使用DCL(Double-Check Locking)双重检查锁机制,


public class MyObject {
    private volatile static MyObject myObject;
    private MyObject(){

    }
    //使用双检查锁机制来解决问题,既保证了不需要同步代码的异步执行性
    //又保证了单例的效果
    public static MyObject getInstance(){
        try{
            if(myObject != null){

            }else{
                //模拟在创建对象之前做一些准备工作
                Thread.sleep(3000);
                synchronized (MyObject.class){
                    if(myObject == null){
                        myObject = new MyObject();
                    }
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return myObject;
    }
}

 

 使用双重检查锁功能,成功地解决了懒汉模式遇到的多线程问题。DCL也是大多数多线程结合单例模式使用的解决方案。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值