单例模式在各个情况下的安全性保证,包括高并发场景下

92 阅读3分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

线程安全性获得保证

工作内存与主内存同步延迟现象导致的可见性问题 可以使用synchronized或volatile关键字解决,它们都可以使一个线程修改后的变量立即对其他线程可见。

对于指令重排导致的可见性问题和有序性问题 可以利用volatile关键字解决,因为volatile的另外一个作用就是禁止重排序优化。

单例模式在多线程环境下可能存在的安全性问题

单机版单例模式

package com.wsx;

public class SingletonDemo {

    private static SingletonDemo instance = null;

    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t"+"我是单例模式的构造方法");
    }

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

    public static void main(String[] args) {

        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());

    }
}
多线程版单例模式
        package com.wsx;

        public class SingletonDemo {

            private static SingletonDemo instance = null;

            private SingletonDemo(){
                System.out.println(Thread.currentThread().getName()+"\t"+"我是单例模式的构造方法");
            }

            public static SingletonDemo getInstance(){//此处加sync可以解决,但是锁了整个方法
                if(instance == null) {
                    instance = new SingletonDemo();
                }
                return instance;
            }

            public static void main(String[] args) {

//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());

                for (int i = 0; i < 10; i++) {
                    new Thread(()->{
                        SingletonDemo.getInstance();
                    },String.valueOf(i)).start();
                }

            }
        }

单例模式volatile分析

DCL(double Check Lock双端检锁机制)

demo1

这种情况很小几率不安全,因为存在指令重排情况

package com.wsx;

        public class SingletonThreadDemo {

            private static SingletonThreadDemo instance = null;

            private SingletonThreadDemo(){
                System.out.println(Thread.currentThread().getName()+"\t"+"我是单例模式的构造方法");
            }

            public static SingletonThreadDemo getInstance(){//此处加sync可以解决,但是锁了整个方法
                //双端检锁机制
                if(instance == null) {

                    synchronized (SingletonDemo.class){

                        if(instance == null){

                            instance = new SingletonThreadDemo();

                        }
                    }

                }
                return instance;
            }

            public static void main(String[] args) {

//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());
//        System.out.println(SingletonDemo.getInstance() == SingletonDemo.getInstance());

                for (int i = 0; i < 10; i++) {
                    new Thread(()->{
                        SingletonThreadDemo.getInstance();
                    },String.valueOf(i)).start();
                }

            }
        }

单例:模式volatle分析

DCL (双端检锁)机制不一定线程安全,原因是有指令重排序的存在,加入volatile可以禁止指令重排 原因在于某- -个线程执行到第一 次检测,读取到的instance不为nul时,instance的引用对象可能没有完成初始化。

instance = new SingletonDemo();可以分为以下3步完成(伪代码)

memory = allocate(); //1.分配对象内存空间

instance(memory); //2. 初始化对象

instance = memory; //3. 设置instance指向刚分配的内存地址,此时instance ! =null

步骤2和步骤3不存在数据依赖关系,而且无论重排前还是重排后程序的执行结果在单线程中并没有改变, 因此这种重排优化是允许的。

memory = allocate(); //1 .分配对象内存空间.

instance = memory; //3.设置instance指向刚分配的内存地址, 此时instance ! =null,, 但是对象还没有初始化完成!

instance(memory); //2.初始化对象

但是指令重排只会保证串行语义的执行的一 致性(单线程),但并不会关心多线程间的语义-致性。 所以当一条线程访问instance不为nul时,由于instance实例未必已初始化完成,也就造成了线程安全问题。

高并发下DCL的volatile的单例模式最终版本
        
package com.wsx;

        public class SingletonThreadDemo {

            //加volatile防止指令重排
            private static volatile SingletonThreadDemo instance = null;

            private SingletonThreadDemo(){
                System.out.println(Thread.currentThread().getName()+"\t"+"我是单例模式的构造方法");
            }

            public static SingletonThreadDemo getInstance(){//此处加sync可以解决,但是锁了整个方法
                //双端检锁机制
                if(instance == null) {

                    synchronized (SingletonThreadDemo.class){

                        if(instance == null){

                            instance = new SingletonThreadDemo();

                        }
                    }

                }
                return instance;
            }

            public static void main(String[] args) {
                for (int i = 0; i < 10; i++) {
                    new Thread(()->{
                        SingletonThreadDemo.getInstance();
                    },String.valueOf(i)).start();
                }

            }
        }