Volatile的应用

单例模式DCL代码

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

/**
* 单例模式
*
* @author xiaocheng
* @date 2020/4/22 9:19
*/
public class Singleton { private static Singleton singleton = null; private Singleton() {
System.out.println(Thread.currentThread().getName() + "\t单例构造方法");
} public static Singleton getInstance() {
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());
}
}

最后输出的结果

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

/**
* 单例模式
*
* @author xiaocheng
* @date 2020/4/22 9:19
*/
public class Singleton { private static Singleton singleton = null; private Singleton() {
System.out.println(Thread.currentThread().getName() + "\t单例构造方法");
} public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
} public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
Singleton.getInstance();
}, String.valueOf(i)).start();
}
}
}

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

解决方法1

引入synchronized关键字

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

输出结果

我们能够发现,通过引入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();
}
}
}

Volatile的应用DCL单例模式(四)的更多相关文章

  1. 从一个小例子引发的Java内存可见性的简单思考和猜想以及DCL单例模式中的volatile的核心作用

    环境 OS Win10 CPU 4核8线程 IDE IntelliJ IDEA 2019.3 JDK 1.8 -server模式 场景 最初的代码 一个线程A根据flag的值执行死循环,另一个线程B只 ...

  2. DCL单例模式

    我们第一次写的单例模式是下面这样的: public class Singleton { private static Singleton instance = null; public static ...

  3. DCL单例模式中的缺陷及单例模式的其他实现

    DCL:Double Check Lock ,意为双重检查锁.在单例模式中懒汉式中可以使用DCL来保证程序执行的效率. 1 public class SingletonDemo { 2 private ...

  4. Volatile关键字&&DCL单例模式,volatile 和 synchronized 的区别

    Volatile 英文翻译:易变的.可变的.不稳定的. 一.volatile 定义及用法 多个线程的工作内存彼此独立,互不可见,线程启动的时候,虚拟机为每个内存分配一块工作内存,不仅包含了线程内部定义 ...

  5. SQL语言DDL DML DCL TCL四种语言

    1.DDL(Data Definition Language)数据库定义语言:DDL使我们有能力创建或删 除表格.可以定义索引(键),规定表之间的链接,以及施加表间的 约束. • 常见DDL 语句: ...

  6. Java关键字volatile的实现原理(四)

    简述 volatile 是轻量级的synchronized,在多线程开发中保证了共享变量的可见性.可见性就是当一个线程修改一个共享变量时,另一个线程可以读到修改的值.如果volatile变量使用恰当, ...

  7. 双重检查锁单例模式为什么要用volatile关键字?

    前言 从Java内存模型出发,结合并发编程中的原子性.可见性.有序性三个角度分析volatile所起的作用,并从汇编角度大致说了volatile的原理,说明了该关键字的应用场景:在这补充一点,分析下v ...

  8. GoF23:单例模式(singleton)

    目录 单例模式简介 常见五种单例模式的实现方式 饿汉式 懒汉式 DCL懒汉式 饿汉式改进(静态内部类式) 枚举单例 防止反射破坏单例模式 单例模式简介 核心作用:保证一个类只有一个实例,并且提供一个访 ...

  9. Java中volatile关键字你真的理解了吗?

    面:你怎样理解volatile关键字时? 我:不加思索的说出,volatile修饰的成员变量,可保证线程可见性.不保证原子性和禁止指令重排. 面:你能谈谈什么是线程可见性吗? 我:各个线程对主内存中共 ...

随机推荐

  1. Nature重磅:华裔科学家成功解码脑电波,AI直接从大脑中合成语音

    [导读]Nature发表华裔作者论文:通过解码大脑活动提升语音的清晰度,使用深度学习方法直接从大脑信号中产生口语句子,达到150个单词,接近正常人水平. 大脑活动能够解码成语音了. 说话似乎是一项毫不 ...

  2. nim博弈 LightOJ - 1253

    主要是写一下nim博弈的理解,这个题有点奇怪,不知道为什么判断奇偶性,如果有大佬知道还请讲解一下. //nim博弈 //a[0]~a[i] 异或结果为k 若k=0 则为平衡态 否则为非平衡态 //平衡 ...

  3. mac 中使用git 和pycharm提交项目

    一.安装Git 1.验证git是否安装: 终端中输入: git 如果安装过出现: 2.安装git: 进入https://git-scm.com: 点击 Download 2.23.0 for Mac ...

  4. [codevs3118]高精度除法<高精度>

    题目链接:http://codevs.cn/problem/3118/ 为了做一道名为国王游戏的贪心,我跑来学习了高精度除法....相传,高精度除法是高精度四个基本运算最难的 但事实上,高精度除法可以 ...

  5. Django REST Framework 教程开篇

    作者:HelloGitHub-追梦人物 欢迎来到 HelloDjango 全栈系列教程第二步--Django REST Framework 教程! 首先恭喜你完成了 HelloDjango 全栈系列教 ...

  6. 1005 Spell It Right (20 分)

    Given a non-negative integer N, your task is to compute the sum of all the digits of N, and output e ...

  7. PTA数据结构与算法题目集(中文) 7-43字符串关键字的散列映射 (25 分)

    PTA数据结构与算法题目集(中文)  7-43字符串关键字的散列映射 (25 分) 7-43 字符串关键字的散列映射 (25 分)   给定一系列由大写英文字母组成的字符串关键字和素数P,用移位法定义 ...

  8. How to setup a Alpine Linux mirror

    How to setup a Alpine Linux mirror   Contents 1 Introduction 2 Setting up the cron job 3 Setting up ...

  9. Loop Unrolling 循环展开

    在csapp第五章5.2中提到了循环展开(loop unrolling).这里展开一下为什么循环展开可以提升程序的效率. 以书中计算数组和的两段代码为例: 1.未展开: void psum1(floa ...

  10. Android 6.0及以上版本如何实现从图库中选取图片和拍照功能

    XML 代码: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andr ...