单例模式中的多线程问题

DCL:(Double Check Lock),双重判断锁, 要知道DCL的由来,先从单例模式说起

单例模式——饿汉式

public class Singleton01 {
    private static final Singleton01 INSTANCE = new Singleton01();
    private Singleton01 (){
    }
    public static Singleton01 getInstance() {
        return INSTANCE;
    }

    public static void main(String[] args) {
        Singleton01 instance1 = getInstance();
        Singleton01 instance2 = getInstance();
        System.out.println(instance1 == instance2);
    }
}

  

饿汉式就是甭管三七二十一,我上来就先把这个对象new出来,构造方法是private的,别人创建不了,通过一个静态的getInstance()方法获取实例,这样能够保证我每次获取的对象都是同一个,这是单例最简单的写法。

这时候有人说了,你这写的不咋地啊,我还没用你这个对象呢,你就给我new出来了,太浪费空间了。

单例模式——懒汉式

/**
 * 单例模式--懒汉式
 */
public class Singleton02 {
    private static Singleton02 INSTANCE;
    private Singleton02(){
    }
    public static Singleton02 getInstance(){
        if (INSTANCE == null) {
            try {
              	// 业务代码
                Thread.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Singleton02();
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(Singleton02.getInstance().hashCode())).start();
        }
    }
}

饱汉式是什么时候使用,我什么时候再给你创建,对象不为空我就直接返回。

这时候又有人说了,你这不对呀,在多线程的情况下是有可能创建多个对象的,什么意思呢?比如有两个线程,第一个线程进来判断INSTANCE是否为空,第一次进来肯定为空,继续执行,在执行业务代码还没有new对象的时候,第二个线程进来,判断INSTANCE是否为空,依然为空,继续往下执行,这时候线程一new了一个对象返回,线程二也继续执行new了一个对象返回,一共创建了两个对象,从上面的程序也能验证出来,hashCode不一致说明不是同一个对象。

 

单例模式——升级版

/**
 * 单例模式--升级版
 */
public class Singleton03 {
    private static Singleton03 INSTANCE;
    private Singleton03(){
    }
    public static synchronized Singleton03 getInstance(){
        if (INSTANCE == null) {
            try {
              	// 业务代码
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            INSTANCE = new Singleton03();
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(Singleton03.getInstance().hashCode())).start();
        }
    }
}

  

我在方法上加了一把锁,每个线程进来必须先拿到这把锁才能执行里面的代码,同一时刻,只能有一个线程在执行,执行完的时候一定能new出一个对象,释放锁后,第二个线程才能进来,判断的时候这个对象一定不为空,在多线程的情况下能够保证对象时唯一的。

这时候又有人问了,你这锁加到方法上了,锁的粒度太大了,我只想在需要加锁的地方加锁行不行。

public class Singleton04 {
    private static Singleton04 INSTANCE;
    private Singleton04(){
    }
    public static Singleton04 getInstance(){
        // 业务代码
        if (INSTANCE == null) {
            synchronized (Singleton04.class) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Singleton04();
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(Singleton04.getInstance().hashCode())).start();
        }
    }
}

  看下上面这段代码,先判断对象是否为空,为空你再上锁,这个能解决多线程下的数据不一致性问题吗?不能

很显然,还是不能保证多线程下的数据不一致性问题,怎么回事呢?

当线程一进入方法判断对象是否为空,为空,然后第一个线程停了,第二线程进来判断是否为空,为空,继续执行,new了一个对象后返回释放锁,线程一拿到锁后继续执行,又new了一个对象。所以终于诞生了美团问的这个写法,DCL。

单例模式——DCL(DOUBLE CHECK LOCK)

public class Singleton05 {
    private static volatile Singleton05 INSTANCE;
    private Singleton05(){
    }
    public static Singleton05 getInstance(){
        // 业务代码
        if (INSTANCE == null) {
            synchronized (Singleton05.class) {
                if (INSTANCE == null) {  // 双重验证
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Singleton05();
                }
            }
        }
        return INSTANCE;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(()-> System.out.println(Singleton05.getInstance().hashCode())).start();
        }
    }
}

  以上这段代码就是DCL

  

posted @ 2020-10-27 18:55  瞌睡青年  阅读(271)  评论(0)    收藏  举报