ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Volatile的应用DCL单例模式

2021-09-07 11:04:34  阅读:186  来源: 互联网

标签:DCL getInstance Singleton instance Volatile 单例 singleton null


多线程环境下的单例模式的并发问题

首先回顾一下,单线程下的单例模式代码

 1 /**
 2  * 单例模式
 3  *
 4  * @author xiaocheng
 5  * @date 2020/4/22 9:19
 6  */
 7 public class Singleton {
 8 
 9     private static Singleton singleton = null;
10 
11     private Singleton() {
12         System.out.println(Thread.currentThread().getName() + "\t单例构造方法");
13     }
14 
15     public static Singleton getInstance() {
16         if (singleton == null) {
17             singleton = new Singleton();
18         }
19         return singleton;
20     }
21 
22     public static void main(String[] args) {
23         System.out.println(Singleton.getInstance() == Singleton.getInstance());
24         System.out.println(Singleton.getInstance() == Singleton.getInstance());
25         System.out.println(Singleton.getInstance() == Singleton.getInstance());
26         System.out.println(Singleton.getInstance() == Singleton.getInstance());
27     }
28 }

 

最后输出的结果

但是在多线程的环境下,我们的单例模式是否还是同一个对象了

 1 /**
 2  * 单例模式
 3  *
 4  * @author xiaocheng
 5  * @date 2020/4/22 9:19
 6  */
 7 public class Singleton {
 8 
 9     private static Singleton singleton = null;
10 
11     private Singleton() {
12         System.out.println(Thread.currentThread().getName() + "\t单例构造方法");
13     }
14 
15     public static Singleton getInstance() {
16         if (singleton == null) {
17             singleton = new Singleton();
18         }
19         return singleton;
20     }
21 
22     public static void main(String[] args) {
23         for (int i = 0; i < 10; i++) {
24             new Thread(() -> {
25                 Singleton.getInstance();
26             }, String.valueOf(i)).start();
27         }
28     }
29 }

 

从下面的结果我们可以看出,我们通过SingletonDemo.getInstance() 获取到的对象,并不是同一个,而是被下面几个线程都进行了创建,那么在多线程环境下,单例模式如何保证呢?

解决方法1

引入synchronized关键字

1     public synchronized static SingletonDemo getInstance() {
2         if(instance == null) {
3             instance = new SingletonDemo();
4         }
5         return instance;
6     }

 

输出结果

我们能够发现,通过引入Synchronized关键字,能够解决高并发环境下的单例模式问题

但是synchronized属于重量级的同步机制,它只允许一个线程同时访问获取实例的方法,但是为了保证数据一致性,而减低了并发性,因此采用的比较少

解决方法2

通过引入DCL Double Check Lock 双端检锁机制

就是在进来和出去的时候,进行检测

    public static SingletonDemo getInstance() {
        if(instance == null) {
            // 同步代码段的时候,进行检测
            synchronized (SingletonDemo.class) {
                if(instance == null) {
                    instance = new SingletonDemo();
                }
            }
        }
        return instance;
    }

 

最后输出的结果为:

从输出结果来看,确实能够保证单例模式的正确性,但是上面的方法还是存在问题的

DCL(双端检锁)机制不一定是线程安全的,原因是有指令重排的存在,加入volatile可以禁止指令重排

原因是在某一个线程执行到第一次检测的时候,读取到 instance 不为null,instance的引用对象可能没有完成实例化。因为 instance = new SingletonDemo();可以分为以下三步进行完成:

  • 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、初始化对象

这样就会造成什么问题呢?

也就是当我们执行到重排后的步骤2,试图获取instance的时候,会得到null,因为对象的初始化还没有完成,而是在重排后的步骤3才完成,因此执行单例模式的代码时候,就会重新在创建一个instance实例

指令重排只会保证串行语义的执行一致性(单线程),但并不会关系多线程间的语义一致性

所以当一条线程访问instance不为null时,由于instance实例未必已初始化完成,这就造成了线程安全的问题

所以需要引入volatile,来保证出现指令重排的问题,从而保证单例模式的线程安全性

private static volatile SingletonDemo instance = null;

最终代码

/**
 * 单例模式
 *
 * @author xiaocheng
 * @date 2020/4/22 9:19
 */
public class Singleton {

    private static volatile Singleton singleton = null;

    private Singleton() {
        System.out.println(Thread.currentThread().getName() + "\t单例构造方法");
    }

    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }

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

 转载自:https://www.cnblogs.com/bbgs-xc/p/12750023.html

标签:DCL,getInstance,Singleton,instance,Volatile,单例,singleton,null
来源: https://www.cnblogs.com/izyh/p/15237233.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有