DCL双重检测实现线程安全的单例模式

一、前言
单例模式在设计模式中使用的较多,也是我学习设计模式最先接触的一个设计模式,虽然容易理解,但是能将其运用于实际应用中却很难。最近也在学习《Java多线程编程核心技术》这本书,如何将多线程与单例模式结合起来是个值得探讨的问题。
二、我对单例模式的理解
单例模式,字面理解就是一个程序中对于一个类,只能有一个类的实例。比如在一个应用程序中用来检测当前登录的用户个数(LoginUser类),产生一个实例LoginUser user = new LoginUser();,对于每一个在调用这个实例来获取当前登录用户个数的应用程序来说,当前登录的人数应该是固定的,每个应用程序获取到的user.size()是一样的才正确,这就是单例模式的简单应用。相反,如果产生了2个实例,在某一时刻有用户退出又有用户登录,可能会导致这2个实例的值不一样,统计到的用户登录人数当然会出错。
三、单例模式与多线程
单例模式又分为饿汉模式和懒汉模式。具体区别为:
(1)**饿汉模式:**在申明对象时立即对其实例化,即在调用方法前,实例已经被创建:private static LoginUser user = new LoginUser();
代码如下:
首先创建实例对象类:

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

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

然后创建线程类:

package dclpackage;

/**
 * @author wangjie
 * @data 2019/1/13 17:06
 * 创建线程类
 */
public class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println(MyObject.getInstance().hashCode());
    }
}

最后创建运行类:

/**
 * @author wangjie
 * @data 2019/1/13 17:07
 * 线程运行类
 */
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();
    }
}

运行结果:

Connected to the target VM, address: '127.0.0.1:56872', transport: 'socket'
1736506725
1736506725
1736506725
Disconnected from the target VM, address:

可见,每个线程实例的hashcode一样,说明只有一个实例。
(2)**懒汉模式:**只在方法被调用时才创建实例。
建立实例类:

/**
 * @author wangjie
 * @data 2019/1/13 17:41
 */
public class MyObject1 {
    public static MyObject1 myObject1;
    private MyObject1(){}
    public static MyObject1 getInstance(){
        //延迟加载
        if(myObject1 != null){
        }else{
            myObject1 = new MyObject1();
        }
        return myObject1;
    }
}

建立线程类:

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

运行类:

public class Run1 {
    public static void main(String[] args) {
        MyThread1 t1 = new MyThread1();
        t1.start();
    }
}

运行结果:

Connected to the target VM, address: '127.0.0.1:57695', transport: 'socket'
677636723

可见,取出了一个实例。
但是这仅仅是在一个线程的情况下,如果在多线程环境中则出错:
修改线程类:

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

运行结果:

Connected to the target VM, address: '127.0.0.1:57761', transport: 'socket'
680061401
680061401
844100537

可见取出的实例并不是一个,发生了线程不安全的情况。需要解决这个问题,可以使用DCL双重检测锁。

四、DCL双重检测实现线程安全的单例模式
修改实例类:

package dclpackage;

/**
 * @author wangjie
 * @data 2019/1/13 17:01
 * 使用DCL双检测锁机制实现单利模式(懒汉模式)
 */
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;
    }
}

再次运行结果:

Connected to the target VM, address: '127.0.0.1:57802', transport: 'socket'
1766268477
1766268477
1766268477

实现了在多线程环境下的懒汉模式。


学习的精髓在于尝试,在于动手实践,才能深入理解!

你可能感兴趣的:(DCL双重检测实现线程安全的单例模式)