网易首页 > 网易号 > 正文 申请入驻

【190期】对 volatile 的理解—从 JMM 以及单例模式剖析

0
分享至

点击上方“Java精选”,选择“设为星标”

别问别人为什么,多问自己凭什么!

下方有惊喜,留言必回,有问必答!

每天08:35更新文章,每天进步一点点...

1.volitale是Java虚拟机提供的一种轻量级的同步机制

三大特性

  • 保证可见性

  • 不保证原子性

  • 禁止指令重排

首先保证可见性

1.1 可见性

概念:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值

package com.yuxue.juc.volatileTest;

/**
* 1验证volatile的可见性
* 1.1 如果int num = 0,number变量没有添加volatile关键字修饰
* 1.2 添加了volatile,可以解决可见性
*/
class VolatileDemo1 {

//自定义的类
public static class MyTest{
//类的内部成员变量num
public int num = 0;
//numTo60 方法,让num值为60
public void numTo60(){
num = 60;
}
}

public static void main(String[] args) {

MyTest myTest = new MyTest();
//第一个线程
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "\t come in");
Thread.sleep(3000);
myTest.numTo60();
System.out.println(Thread.currentThread().getName() + "\t update value:" + myTest.num);
} catch (InterruptedException e) {
e.printStackTrace();
}
} ,"thread1").start();;

//主线程判断num值
while (myTest.num == 0){
//如果myData的num一直为零,main线程一直在这里循环
}
System.out.println(Thread.currentThread().getName() + "\t mission is over, num value is " + myTest.num);
}
}

如上代码是没有保证可见性的,可见性存在于JMM当中即java内存模型当中的,可见性主要是指当一个线程改变其内部的工作内存当中的变量后,其他线程是否可以观察到,因为不同的线程件无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,因为此处没有添加volatile指令,导致其中thread1对num值变量进行更改时,main线程无法感知到num值发生更改,导致在while处无限循环,读不到新的num值,会发生死循环

此时修改类中代码为


* volatile可以保证可见性,及时通知其他线程,主物理内存的值已经被修改
public static class MyTest{
//类的内部成员变量num
public volatile int num = 0;
//numTo60 方法,让num值为60
public void numTo60(){
num = 60;

此时volatile就可以保证内存的可见性,此时运行代码就可以发现

1.2 不保证原子性

原子性概念:不可分割、完整性,即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败

类代码为:

//自定义的类
public static class MyTest {
//类的内部成员变量num
public volatile int num = 0;

public void numPlusPlus() {
num++;
}
}

主方法为

public static void main(String[] args) {
MyTest myTest = new MyTest();
* 10个线程创建出来,每个线程执行2000次num++操作
* 我们知道,在字节码及底层,i++被抽象为三个操作
* 即先取值,再自加,再赋值操作
for (int i = 1; i <= 10; i++) {
new Thread(() -> {
for (int j = 0; j < 2000; j++) {
myTest.numPlusPlus();
}, "Thread" + i).start();
//这里规定线程数大于2,一般有GC线程以及main主线程
while (Thread.activeCount() > 2) {
Thread.yield();
System.out.println(Thread.currentThread().getName() + "\t finally num value is " + myTest.num);

代码如上所示,如果volatile保证原子性,那么10个线程分别执行自加2000次操作,那么最终结果一定是20000,但是执行三次结果如下

//第一次
main finally num value is 19003
//第二次
main finally num value is 18694
//第三次
main finally num value is 19552

可以发现,我们num的值每次都不相同,且最后的值都没有达到20000,这是为什么呢?

首先,我们要考虑到这种情况,假如线程A执行到第11行即myTest.numPlusPlus();方法时

线程进入方法执行numPlusPlus方法后,num的值不管是多少,线程A将num的值首先初始化为0(假如主存中num的值为0),之后num的值自增为1,之后线程A挂起,线程B此时也将主存中的num值读到自己的工作内存中值为0,之后num的值自增1,之后线程B挂起,线程A继续运行将num的值写回主存,但是因为volatile关键字保证可见性,但是在很短的时间内,线程B也将num的值写回主存,此时num的值就少加了一次,所以最后总数基本上少于20000

但是JUC有线程的原子类为AtomicInteger类,此时,将类代码更改为

public static class MyTest {
//类的内部成员变量num
public volatile int num = 0;
AtomicInteger atomicInteger = new AtomicInteger();

//numTo60 方法,让num值为60
public void numTo60() {
num = 60;
}

public void numPlusPlus() {
num++;
}
public void myAtomPlus(){
atomicInteger.getAndIncrement();
}
}

共同测试num和atomicInteger,此时执行主函数,三次结果为

//第一次
main finally num value is 19217
main finally atomicInteger value is 20000
//第二次
main finally num value is 19605
main finally atomicInteger value is 20000
//第三次
main finally num value is 18614
main finally atomicInteger value is 20000

我们发现volatile关键字并没有保证我们的变量的原子性,但是JUC内部的AtomicInteger类保证了我们变量相关的原子性,AtomicInteger底层用到了CAS。

1.3 禁止指令重排

有序性的概念:在计算机执行程序时,为了提高性能,编译器和处理器常常会对指令做重排,一般分以下三种

单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致。

处理器在进行重排顺序是必须要考虑指令之间的数据依赖性

多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性时无法确定的,结果无法预测

重排代码实例:声明变量:int a,b,x,y=0

如果编译器对这段程序代码执行重排优化后,可能出现如下情况:

这个结果说明在多线程环境下,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性是无法确定的

volatile实现禁止指令重排,从而避免了多线程环境下程序出现乱序执行的现象

内存屏障(Memory Barrier)又称内存栅栏,是一个CPU指令,他的作用有两个:

  • 保证特定操作的执行顺序

  • 保证某些变量的内存可见性(利用该特性实现volatile的内存可见性)

由于编译器和处理器都能执行指令重排优化。如果在之间插入一条Memory Barrier则会告诉编译器和CPU, 不管什么指令都不能和这条Memory Barrier指令重排顺序,也就是说通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化。内存屏障另外一个作用是强制刷出各种CPU的缓存数据,因此任何CPU上的线程都能读 取到这些数据的最新版本

2.JMM(java内存模型)

为什么提到JMM?JMM当中规定了可见性、原子性、以及有序性的问题,在多线程中只要保证了以上问题的正确性,那么基本上不会发生多线程当中存在数据安全问题

JMM(Java Memory Model)本身是一种抽象的概念,并不真实存在,他描述的时一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

JMM关于同步的规定:

  • 线程解锁前,必须把共享变量的值刷新回主内存

  • 线程加锁前,必须读取主内存的最新值到自己的工作内存

  • 加锁解锁时同一把锁

由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有的成为栈空间),工作内存是每个线程的私有数据区域,而java内存模型中规定所有变量都存储在主内存,主内存是贡献内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先概要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存的变量副本拷贝,因此不同的线程件无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,期间要访问过程如下图:

JMM的三大特性

  • 可见性

  • 原子性

  • 有序性

所以JMM当中的2.1和2.3在volatile当中都有很好的体现,volatile关键字并不能保证多线程当中的原子性,但是volatile是轻量级的同步机制,不想synchronized锁一样粒度太大

3.你在那些地方用过volatile?结合实际谈论一下?

当普通单例模式在多线程情况下:


* 普通单例模式
public class SingletonDemo {
private static SingletonDemo instance = null;
private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + "\t 构造方法 SingletonDemo()");
public static SingletonDemo getInstance() {
if (instance == null) {
instance = new SingletonDemo();
return instance;
public static void main(String[] args) {
//构造方法只会被执行一次
// System.out.println(getInstance() == getInstance());
// System.out.println(getInstance() == getInstance());
// System.out.println(getInstance() == getInstance());
//并发多线程后,构造方法会在一些情况下执行多次
for (int i = 0; i < 10; i++) {
new Thread(() -> {
SingletonDemo.getInstance();
}, "Thread " + i).start();

此时会出现两个线程运行了SingletonDemo的构造方法

此时就违反了单例模式的规定,其构造方法在一些情况下会被执行多次

解决方式:

单例模式DCL代码

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 SingleDemo();可以被分为一下三步(伪代码):


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

所以如果3步骤提前于步骤2,但是instance还没有初始化完成指令重排只会保证串行语义的执行的一致性(单线程),但并不关心多线程间的语义一致性。

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

此时加上volatile后就不会出现线程安全问题

private static volatile SingletonDemo instance = null;

因为volatile禁止了指令重排序的问题

作者:浴血 https://www.cnblogs.com/yuxueyyz/p/14972113.html

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!

------ THE END ------

精品资料,超赞福利!


3000+ 道面试题在线刷,最新、最全 Java 面试题!

期往精选 点击标题可跳转

技术交流群!

最近有很多人问,有没有读者&异性交流群,你懂的!想知道如何加入。加入方式很简单,有兴趣的同学,只需要点击下方卡片,回复“加群”,即可免费加入交流群!

文章有帮助的话,在看,转发吧!

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
黑龙江庆安县警方:一辆小型客车在鹤哈高速发生单方事故,致5死12伤

黑龙江庆安县警方:一辆小型客车在鹤哈高速发生单方事故,致5死12伤

界面新闻
2024-06-30 11:59:39
《机器猫》主要人物家庭条件排名,看看谁家最有钱

《机器猫》主要人物家庭条件排名,看看谁家最有钱

坠入二次元的海洋
2024-06-30 14:46:29
Woj:托平四年6000万留守步行者

Woj:托平四年6000万留守步行者

直播吧
2024-07-01 01:48:21
萨卡踢左翼卫了!英格兰终于换人:特里皮尔伤退,帕尔默登场!

萨卡踢左翼卫了!英格兰终于换人:特里皮尔伤退,帕尔默登场!

直播吧
2024-07-01 01:37:14
国防部最新指示:全军聚焦,备战打仗,谁与争锋?

国防部最新指示:全军聚焦,备战打仗,谁与争锋?

趣说世界哈
2024-06-28 18:00:45
120万一针抗癌药,安徽首位患者治疗成功,30天肿瘤消失,值得吗

120万一针抗癌药,安徽首位患者治疗成功,30天肿瘤消失,值得吗

39健康网
2024-06-30 20:30:03
索斯盖特的阵眼?英格兰半场0-1落后,菲利普斯登上英国区热搜

索斯盖特的阵眼?英格兰半场0-1落后,菲利普斯登上英国区热搜

直播吧
2024-07-01 01:05:17
中国游客去日本狂买新三样:抛弃日本马桶盖和电饭煲...

中国游客去日本狂买新三样:抛弃日本马桶盖和电饭煲...

快科技
2024-06-28 16:36:05
湖南高速警察:只出不进!岳阳高速这些入口实行临时管制

湖南高速警察:只出不进!岳阳高速这些入口实行临时管制

潇湘晨报
2024-06-30 10:41:07
深度调查:张维为日内瓦大学亚洲研究中心“高级研究员”身份存疑

深度调查:张维为日内瓦大学亚洲研究中心“高级研究员”身份存疑

Jim博士
2024-06-30 12:32:06
还是有正义的!看了国足18强分组,足联主席“大发雷霆”公然指出

还是有正义的!看了国足18强分组,足联主席“大发雷霆”公然指出

人生趣事悟语
2024-06-30 04:15:09
再见,巴黎!6年5亿天价年薪挖角,皇马天王不去,要与姆巴佩协作

再见,巴黎!6年5亿天价年薪挖角,皇马天王不去,要与姆巴佩协作

叁炮体育
2024-06-30 15:40:10
对胡友平的感恩,挤满了日本评论区

对胡友平的感恩,挤满了日本评论区

环行星球
2024-06-29 18:57:58
俄罗斯用血的代价警示中国:一旦爆发战争,一定要牢记这三大铁律

俄罗斯用血的代价警示中国:一旦爆发战争,一定要牢记这三大铁律

花仙历史说
2024-06-30 07:57:35
霍思燕代孕女儿近照曝光!8岁女儿颜值逆天,身穿裙子戴近视眼镜

霍思燕代孕女儿近照曝光!8岁女儿颜值逆天,身穿裙子戴近视眼镜

八卦王者
2024-06-29 16:30:58
A股:重磅经济数据发布!超级赛道利空消息,下周大盘将这样运行

A股:重磅经济数据发布!超级赛道利空消息,下周大盘将这样运行

云姐财说
2024-06-30 18:07:38
美国会通过丧心病狂法案,不是支持以色列,是要置以色列于死地

美国会通过丧心病狂法案,不是支持以色列,是要置以色列于死地

战域笔墨
2024-06-30 20:06:08
7万人见证,34岁大师无法退役,4项全场第1:足坛90后第一人 !

7万人见证,34岁大师无法退役,4项全场第1:足坛90后第一人 !

风过乡
2024-06-30 08:53:43
日本公布2024年6月最受欢迎女优TOP20 森日向子美园和花入十强

日本公布2024年6月最受欢迎女优TOP20 森日向子美园和花入十强

孤独的独角兽影视
2024-06-27 09:50:02
恶心,男子入住酒店不到10小时被要求退房,前台:全广州都这样

恶心,男子入住酒店不到10小时被要求退房,前台:全广州都这样

说真话的小陈
2024-06-30 10:55:58
2024-07-01 03:04:49
Java精选
Java精选
一场永远也演不完的戏
1551文章数 3855关注度
往期回顾 全部

科技要闻

河南火箭坠落爆炸?商业航天公司回应了

头条要闻

英格兰本届首次落后 施兰茨3球登顶射手榜

头条要闻

英格兰本届首次落后 施兰茨3球登顶射手榜

体育要闻

“意大利很弱”,不再是错觉了

娱乐要闻

白玉兰明星反应精彩 胡歌获奖唐嫣激动

财经要闻

A股上半年人均亏损1.2万 你亏了多少?

汽车要闻

小鹏MONA M03 7月3日首发 15万紧凑级

态度原创

旅游
艺术
数码
教育
公开课

旅游要闻

突发!上海出发豪华邮轮,男子翻越栏杆后落海

艺术要闻

穿越时空的艺术:《马可·波罗》AI沉浸影片探索人类文明

数码要闻

回应小米空调第一不实后!董明珠:不是我刻意要当网红 格力的产品深入人心

教育要闻

TTS新传论文带读:所有女性!!!我们终于被看见了!!终于被关注了!!

公开课

连中三元是哪三元?

无障碍浏览 进入关怀版