面试突击51:为什么单例一定要加 volatile?

.a30322568ca622050809596a22033841.png

作者 | 磊哥

来源 | Java面试真题解析(ID:aimianshi666)

转载请联系授权(微信ID:GG_Stone)

单例模式的实现方法有很多种,如饿汉模式、懒汉模式、静态内部类和枚举等,当面试官问到“为什么单例模式一定要加 volatile?”时,那么他指的是为什么懒汉模式中的私有变量要加  volatile?

懒汉模式指的是对象的创建是懒加载的方式,并不是在程序启动时就创建对象,而是第一次被真正使用时才创建对象。

要解释为什么要加 volatile?我们先来看懒汉模式的具体实现代码:

public class Singleton {// 1.防止外部直接 new 对象破坏单例模式private Singleton() {}// 2.通过私有变量保存单例对象【添加了 volatile 修饰】private static volatile Singleton instance = null;// 3.提供公共获取单例对象的方法public static Singleton getInstance() {if (instance == null) { // 第 1 次效验synchronized (Singleton.class) {if (instance == null) { // 第 2 次效验instance = new Singleton(); }}}return instance;}
}

从上述代码可以看出,为了保证线程安全和高性能,代码中使用了两次 if 和 synchronized 来保证程序的执行。那既然已经有 synchronized 来保证线程安全了,为什么还要给变量加 volatile 呢?在解释这个问题之前,我们先要搞懂一个前置知识:volatile 有什么用呢?

1.volatile 作用

volatile 有两个主要的作用,第一,解决内存可见性问题,第二,防止指令重排序。

1.1 内存可见性问题

所谓内存可见性问题,指的是多个线程同时操作一个变量,其中某个线程修改了变量的值之后,其他线程感知不到变量的修改,这就是内存可见性问题。而使用 volatile 就可以解决内存可见性问题,比如以下代码,当没有添加 volatile 时,它的实现如下:

private static boolean flag = false;
public static void main(String[] args) {Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {// 如果 flag 变量为 true 就终止执行while (!flag) {}System.out.println("终止执行");}});t1.start();// 1s 之后将 flag 变量的值修改为 trueThread t2 = new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("设置 flag 变量的值为 true!");flag = true;}});t2.start();
}

以上程序的执行结果如下:c0682cd1209e2141db3ee9b495bac562.png然而,以上程序执行了 N 久之后,依然没有结束执行,这说明线程 2 在修改了 flag 变量之后,线程 1 根本没有感知到变量的修改。那么接下来,我们尝试给 flag 加上 volatile,实现代码如下:

public class volatileTest {private static volatile boolean flag = false;public static void main(String[] args) {Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {// 如果 flag 变量为 true 就终止执行while (!flag) {}System.out.println("终止执行");}});t1.start();// 1s 之后将 flag 变量的值修改为 trueThread t2 = new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("设置 flag 变量的值为 true!");flag = true;}});t2.start();}
}

以上程序的执行结果如下:411c1f873d77080eba78fbc11e412345.png从上述执行结果我们可以看出,使用 volatile 之后就可以解决程序中的内存可见性问题了。

1.2 防止指令重排序

指令重排序是指在程序执行过程中,编译器或 JVM 常常会对指令进行重新排序,已提高程序的执行性能。指令重排序的设计初衷确实很好,在单线程中也能发挥很棒的作用,然而在多线程中,使用指令重排序就可能会导致线程安全问题了。

所谓线程安全问题是指程序的执行结果,和我们的预期不相符。比如我们预期的正确结果是 0,但程序的执行结果却是 1,那么这就是线程安全问题。

而使用 volatile 可以禁止指令重排序,从而保证程序在多线程运行时能够正确执行。

2.为什么要用 volatile?

回到主题,我们在单例模式中使用 volatile,主要是使用 volatile 可以禁止指令重排序,从而保证程序的正常运行。这里可能会有读者提出疑问,不是已经使用了 synchronized 来保证线程安全吗?那为什么还要再加 volatile 呢?看下面的代码:

public class Singleton {private Singleton() {}// 使用 volatile 禁止指令重排序private static volatile Singleton instance = null;public static Singleton getInstance() {if (instance == null) { // ①synchronized (Singleton.class) {if (instance == null) {instance = new Singleton(); // ②}}}return instance;}
}

注意观察上述代码,我标记了第 ① 处和第 ② 处的两行代码。给私有变量加 volatile 主要是为了防止第 ② 处执行时,也就是“instance = new Singleton()”执行时的指令重排序的,这行代码看似只是一个创建对象的过程,然而它的实际执行却分为以下 3 步:

  1. 创建内存空间。

  2. 在内存空间中初始化对象 Singleton。

  3. 将内存地址赋值给 instance 对象(执行了此步骤,instance 就不等于 null 了)。

试想一下,如果不加 volatile,那么线程 1 在执行到上述代码的第 ② 处时就可能会执行指令重排序,将原本是 1、2、3 的执行顺序,重排为 1、3、2。但是特殊情况下,线程 1 在执行完第 3 步之后,如果来了线程 2 执行到上述代码的第 ① 处,判断 instance 对象已经不为 null,但此时线程 1 还未将对象实例化完,那么线程 2 将会得到一个被实例化“一半”的对象,从而导致程序执行出错,这就是为什么要给私有变量添加 volatile 的原因了。

总结

使用 volatile 可以解决内存可见性问题和防止指令重排序,我们在单例模式中使用 volatile 主要是使用 volatile 的后一个特性(防止指令重排序),从而避免多线程执行的情况下,因为指令重排序而导致某些线程得到一个未被完全实例化的对象,从而导致程序执行出错的情况。

是非审之于己,毁誉听之于人,得失安之于数。

公众号:Java面试真题解析

面试合集:https://gitee.com/mydb/interview

017e29dd1d1154930d1c98628dab0c3c.gif

往期推荐

87e1c5fa7abe7b83dd457432b426cfd8.png

面试突击50:单例模式有几种写法?


a1580865baceddfc4326f9cff4d0d599.png

面试突击49:说一下 JUC 中的 Exchange 交换器?


c6708de069f3c9b9a27973bb3800ced0.png

面试突击48:死锁的排查工具有哪些?


89fe76f38b6c8a461c2f53f71b7b6c2d.gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/544136.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

聊聊保证线程安全的10个小技巧

前言对于从事后端开发的同学来说,线程安全问题是我们每天都需要考虑的问题。线程安全问题通俗的讲:主要是在多线程的环境下,不同线程同时读和写公共资源(临界资源),导致的数据异常问题。比如:变…

Raid控制器

转载于:https://blog.51cto.com/xuepengdou/1699799

并行计算机架构_计算机科学组织| 并行处理

并行计算机架构并行处理 (Parallel Processing) Parallel processing is processing of the data concurrently. We process the data concurrently to fulfill the demands of the increasingly high performance so that to achieve better throughput instead of processing…

15个必知的Mysql索引失效场景,别再踩坑了!

背景 无论你是技术大佬,还是刚入行的小白,时不时都会踩到Mysql数据库不走索引的坑。常见的现象就是:明明在字段上添加了索引,但却并未生效。前些天就遇到一个稍微特殊的场景,同一条SQL语句,在某些参数下生效…

干掉 Swagger UI,这款神器更好用、更高效!

事情是这样的:今天我们公司的后端说他接口写完了,并分享了一个接口文档给我。用的就是 Swagger UI 自动生成的那种接口文档,就像这种:这种 Swagger UI文档我每次看着就头大,毛病多多查看多级模型时要一级级点开在接口数…

Android UI ActionBar功能-ActionBarProvider的使用

分享功能是很多App都有一个功能,ActionBarProvider可以实现分享功能: 3.0以前的版 本和3.0以后的版 本的区别: public class MainActivity extends Activity {private ShareActionProvider provider;Overrideprotected void onCreate(Bundle …

面渣逆袭:MyBatis连环20问,这谁顶得住?

大家好,今天我们的主角是MyBatis,作为当前国内最流行的ORM框架,是我们这些crud选手最趁手的工具,赶紧来看看面试都会问哪些问题吧。基础1.说说什么是MyBatis?MyBatis logo先吹一下:Mybatis 是一个半 ORM(对…

高并发下如何防重?

前言最近测试给我提了一个bug,说我之前提供的一个批量复制商品的接口,产生了重复的商品数据。追查原因之后发现,这个事情没想象中简单,可以说一波多折。1. 需求产品有个需求:用户选择一些品牌,点击确定按钮…

面试突击55:delete、drop、truncate有什么区别?

作者 | 磊哥来源 | Java面试真题解析(ID:aimianshi666)转载请联系授权(微信ID:GG_Stone)在 MySQL 中,删除的方法总共有 3 种:delete、truncate、drop,而三者的用法和使用…

大厂也在用的 6种 数据脱敏方案,别做泄密内鬼

最近连着几天晚上在家总是接到一些奇奇怪怪的电话,“哥,你是 xxx 吧,我们这里是 xxx 高端男士私人会所...”,握草,我先是一愣,然后狠狠的骂了回去。一脸傲娇的转过头,面带微笑稍显谄媚&#xff…

在Python中使用OpenCV裁剪图像

What is Cropping? 什么是播种? Cropping is the removal of unwanted outer areas from a photographic or illustrated image. The process usually consists of the removal of some of the peripheral areas of an image to remove extraneous trash from the…

面渣逆袭:RocketMQ二十三问

1.为什么要使用消息队列呢?消息队列主要有三大用途,我们拿一个电商系统的下单举例:解耦:引入消息队列之前,下单完成之后,需要订单服务去调用库存服务减库存,调用营销服务加营销数据……引入消息…

Java日志性能那些事(转)

在任何系统中,日志都是非常重要的组成部分,它是反映系统运行情况的重要依据,也是排查问题时的必要线索。绝大多数人都认可日志的重要性,但是又有多少人仔细想过该怎么打日志,日志对性能的影响究竟有多大呢?…

33岁程序员的年中总结

作者 | 磊哥来源 | Java中文社群(ID:javacn666)转载请联系授权(微信ID:GG_Stone)人生在不同的阶段会有不同的生活方式和思考问题的角度,这是一件非常有趣的事~ 比如,我在 22 岁会想&…

数据科学中的简单线性回归

简单线性回归 (Simple Linear Regression) A simple regression model could be a linear approximation of a causative relationship between two or additional variables. Regressions models are extremely valuable, as theyre one in every of the foremost common ways…

鹅厂一面,有关 ThreadLocal 的一切

1. 底层结构ThreadLocal 底层有一个默认容量为 16 的数组组成,k 是 ThreadLocal 对象的引用,v 是要放到 TheadLocal 的值public void set(T value) {Thread t Thread.currentThread();ThreadLocalMap map getMap(t);if (map ! null)map.set(this, valu…

面试突击58:truncate、delete和drop的6大区别!

作者 | 磊哥来源 | Java面试真题解析(ID:aimianshi666)转载请联系授权(微信ID:GG_Stone)在 MySQL 中,使用 truncate、delete 和 drop 都可以实现表删除,但它们 3 个的使用场景和执行…

智力游戏

【Description】whitecloth 最近迷上了一个你小时候已经玩厌了的游戏:移火柴棒。他现在吵着要你陪他玩,你没有办法,只好写一个程序来完成这个工作了。你被给出了一个火柴拼成的等式,比如说下面这个:( 5 7 …

面渣逆袭:MySQL六十六问!建议收藏

基础MySQ Logo作为SQL Boy,基础部分不会有人不会吧?面试也不怎么问,基础掌握不错的小伙伴可以跳过这一部分。当然,可能会现场写一些SQL语句,SQ语句可以通过牛客、LeetCode、LintCode之类的网站来练习。1. 什么是内连接…

try-with-resources 中的一个坑,注意避让

小伙伴们好呀,昨天复盘以前做的项目(大概有一年了),看到这个 try-catch ,又想起自己之前掉坑的这个经历 ,弄了个小 demo 给大家感受下~ 😄问题1一个简单的下载文件的例子。这里会出现什么情况…