一、场景简述

单例模式下有饿汉模式和懒汉模式,其中懒汉模式在于调用相关方法时实例才被创建。懒汉模式我们不难实现,但是在懒汉模式下我们如果使用多线程,就会取出多个实例的情况,与单例模式相违背,所以该篇博客笔者主要关于在多线程环境下利用DCL双检查锁机制来实现懒汉模式。


二、场景实现

1、多线程环境下的懒汉模式实现“错误的单例模式”

MyObject类

package singleton;

/**
 * @author: linjie
 * @description: 懒汉模式
 * @create: 2018/10/07 13:29
 */
public class MyObject {
    private static MyObject myObject;
    private MyObject(){}
    public static MyObject getInstance(){
        try {
            //懒汉模式
            if (myObject != null){
            }else {
                //模拟创建对象之前做的准备性工作
                Thread.sleep(3000);
                myObject = new MyObject();
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return myObject;
    }
}

MyThread类

package singleton;

/**
 * @author: linjie
 * @description: 线程类
 * @create: 2018/10/07 13:32
 */
public class MyThread extends Thread{
    @Override
    public void run(){
        //打印hashcode值
        System.out.println(MyObject.getInstance().hashCode());
    }
}

Run启动类(实现多个线程)

package singleton;

/**
 * @author: linjie
 * @description:启动类
 * @create: 2018/10/07 13:34
 */
public class Run {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        myThread.start();
        myThread1.start();
        myThread2.start();
    }
}

运行结果,可以看到是不同的hashcode值,违背了单例模式

Java多线程环境下的懒汉模式解决方案-LMLPHP

2、使用synchronized同步方法/同步代码块实现多线程下的懒汉模式

只需修改MyObject类

使用同步方法

package singleton;

/**
 * @author: linjie
 * @description: 懒汉模式
 * @create: 2018/10/07 13:29
 */
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;
    }
}

使用同步代码块

package singleton;

/**
 * @author: linjie
 * @description: 懒汉模式
 * @create: 2018/10/07 13:29
 */
public class MyObject {
    private static MyObject myObject;
    private MyObject(){}
    public static MyObject getInstance(){
        try {
            synchronized (MyObject.class){
                if (myObject != null){
                }else {
                    //模拟创建对象之前做的准备性工作
                    Thread.sleep(3000);
                    myObject = new MyObject();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        return myObject;
    }
}

然而通过以上两种方法的确实现了单例,获取的hashcode也是同一个值,但是他们这样的写法,是全部代码都是同步的,这就大大降低了运行效率,所以才有某些重要的代码进行单独的同步,而其他代码则不需要同步,这样运行效率可以大大提升,但是会导致无法解决得到同一个实例对象的结果,如下

package singleton;

/**
 * @author: linjie
 * @description: 懒汉模式
 * @create: 2018/10/07 13:29
 */
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;
    }
}

运行结果,即使效率提升,但还是违背了单例模式

Java多线程环境下的懒汉模式解决方案-LMLPHP

3、使用DCL双检查锁机制实现多线程下的懒汉模式

所以我们就使用DCL双检查锁机制来实现多线程环境下的懒汉模式了

MyObject类

package singleton;

/**
 * @author: linjie
 * @description: 此版本代码称为双重检查 Double-Check Locking
 * @create: 2018/10/07 13:50
 */
public class MyObject {
    private 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;
    }
}

MyThread类

package singleton;

/**
 * @author: linjie
 * @description: 线程类
 * @create: 2018/10/07 13:32
 */
public class MyThread extends Thread{
    @Override
    public void run(){
        //打印hashcode值
        System.out.println(MyObject.getInstance().hashCode());
    }
}

Run启动类

package singleton;

/**
 * @author: linjie
 * @description:启动类
 * @create: 2018/10/07 13:34
 */
public class Run {
    public static void main(String[] args){
        MyThread myThread = new MyThread();
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();
        myThread.start();
        myThread1.start();
        myThread2.start();
    }
}

运行结果,可以看到使用了DCL双检查机制后,实现了单例的结果,并且是部分代码同步,大大提升了运行效率,何乐而不为。

Java多线程环境下的懒汉模式解决方案-LMLPHP


三、参考文献

《Java Multi-thread Programming》

10-07 15:40