欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-05-04 20:59:54
...

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

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;
相关标签: 并发