java多线程: volatile与synchronized在单例模式中的使用

背景

在单例模式的设计中,一般会用到两种方式:

立即加载 : 在类加载初始化的时候就主动创建实例;也称为饿汉式加载。
延迟加载 : 等到真正使用的时候才去创建实例,不用时不去主动创建。也称为懒汉式加载。

//饿汉式加载
	//立即创建对象
    private static SingletonThread singletonThread = new SingletonThread ();
    
    private SingletonThread (){}

    public static SingletonThread getInstance(){
        return singletonThread ;
    }

//懒汉式加载
	private static SingletonThread instance;
	//使用用才创建对象
    public static SingletonThread getInstance() {
        if (instance == null) {
                instance = new SingletonThread();
        }
        return instance;
    }

饿汉式加载单例模式在多线程环境中的问题

/**
 * 懒汉式单例模式在多线程安全的问题
 *
 * @author zhuhuix
 * @date 2020-05-08
 */
public class SingletonThread {

    static SingletonThread instance;

    static SingletonThread getInstance() {
        if (instance == null) {
                instance = new SingletonThread();
        }
        return instance;
    }

    public static void main(String[] args) {

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                SingletonThread singletonThread = SingletonThread.getInstance();
                //打印线程获取的hash值,用来判断返回的是否是同一单例
                System.out.println(Thread.currentThread().getName() + ":" + singletonThread.hashCode());
            }).start();
        }

    }

}

多次运行以上程序,会出现以下BUG,第一个线程得到的实例的hash值与其他线程的得到的hash不一致,单例模式未生效。
在这里插入图片描述

原因分析
  • 饿汉式加载单例模式在单例类创建时就完成了对象的初始化,在多线程环境下,线程访问单例时对象就创建完成了,所以线程天生就是安全的。
  • 而懒汉式加式模式是使用单例类时才完成对象的初始化,在多线程环境下,多个线程会同时进入到 if (instance == null) { instance = new SingletonThread();}很有可能造成创建出多个实例,违背了单例模式的初衷。
synchronized同步与volatile修饰

那怎么解决以上代码中有可能出现的多个实例的问题呢?可以添加synchronized同步锁与volatile修饰静态变量来解决(synchronized的原理可参见 java多线程:synchronized的深度理解)(volatile的原理可参见java多线程:volatile的深度理解

/**
 * 懒汉式单例模式在多线程安全的问题
 *
 * @author zhuhuix
 * @date 2020-05-08
 */
public class SingletonThread {

	//使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个	不完整的实例
    static volatile SingletonThread instance;

    static SingletonThread getInstance() {
        if (instance == null) {
            //对代码块线程同步锁,进行双重判断
            synchronized (SingletonThread.class) {
                if (instance == null) {
                    instance = new SingletonThread();
                }
            }
            //
        }
        return instance;
    }

    static void destroy() {
        instance = null;
    }

    public static void main(String[] args) throws InterruptedException {

        for (int k = 0; k < 100; k++) {
            System.out.println("第" + (k + 1) + "次");
            Thread[] threads = new Thread[10];

            for (int i = 0; i < 10; i++) {
                threads[i] = new Thread(() -> {
                    SingletonThread singletonThread = SingletonThread.getInstance();
                    //打印线程获取的hash值,用来判断返回的是否是同一单例
                    System.out.println(Thread.currentThread().getName() + ":" + singletonThread.hashCode());

                });

            }

            for (int i = 0; i < 10; i++) {
                threads[i].start();
            }

            for (int i = 0; i < 10; i++) {
                threads[i].join();
            }

            SingletonThread.destroy();

        }
    }

}

总结

为保证程序的执行效率 ,考虑只对部分代码块用synchronized实现同步锁,并且用volatile进行修饰静态变量,这种做法无疑是优秀的。

原创文章 56 获赞 8 访问量 4719

猜你喜欢

转载自blog.csdn.net/jpgzhu/article/details/105988223