并发之懒汉饿汉的单例模式线程安全问题

情到浓时终转凉″ 提交于 2019-12-22 21:41:21

饿汉模式:
本身线程安全,在类加载时就已经进行了实例化,无论之后用不用的到。

package com.cljtest.demo.thread;

public class HungerSingleton {
    public static HungerSingleton hungerSingleton = new HungerSingleton();

    public static HungerSingleton getInstance(){
        return hungerSingleton;
    }

    //构造方法私有化
    private HungerSingleton(){

    }

    public static void main(String[] args) {
        //起10个线程进行结果测试
        for(int i = 0;i<10;i++){
            new Thread(()->{
                System.out.println(HungerSingleton.getInstance());
            }).start();
        }
    }
}

一定要注意无参构造方法的私有化问题,如果

设成了public,其他类里实例化时直接new一个无参的构造方法,会造成单例模式失去意义。
由于类加载时就已经实例化了,所以下面的的一切都是基于类加载时创建的对象进行的操作,所以线程安全,都是同一个实例化对象。
运行结果如下:在这里插入图片描述
可以看到地址都是同一个,没有改变。
懒汉模式:
是在需要的时候才实例化,最简单的写法是线程不安全的。

package com.cljtest.demo.thread;

public class LazySingleton {
    public static LazySingleton lazySingleton = null;

    private LazySingleton(){

    }

    public static LazySingleton getInstance() throws InterruptedException {
        if(null == lazySingleton){
            //模拟操作中的停顿
            Thread.sleep(1000L);
            lazySingleton = new LazySingleton();
        }
        return lazySingleton;
    }

    public static void main(String[] args) {
        for(int i = 0;i<10;i++){
            new Thread(()->{
                try {
                    System.out.println(LazySingleton.getInstance());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

可以看到结果,地址几乎均不相同,每次的实例化都是一个新的对象,线程是不安全的。
在这里插入图片描述
如果想要将懒汉模式变成线程安全的,需要对实例化的操作进行加锁处理。需要将getInstance()方法进行如下修改。

public static LazySingleton getInstance() throws InterruptedException {
    if(null == lazySingleton){
        //模拟操作中的停顿
        Thread.sleep(1000L);
        //为了线程安全,需要加锁
        synchronized (LazySingleton.class) {
            //双重判定,对象没实例化时再创建
            if(null == lazySingleton) {
                lazySingleton = new LazySingleton();
            }
        }
    }
    return lazySingleton;
}

这时候运行会发现基本没有问题了,输出的地址都是一个。
但是对这个类进行反编译可能会进行指令重排序的操作,也会造成线程的不安全,所以需要将变量加上volatile关键字进行修饰,他有禁止指令重排序的作用。

public static volatile LazySingleton lazySingleton = null;
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!