多线程高并发下 引入的关键字 volatile

volatile的特性:

1、不保证原子性
2、保证多线程下的资源可见性
3、禁止指令重排
4、只能修饰 全局变量或者静态全局变量

下面展开介绍上面的四大特性

保证多线程下的资源可见性:

首先,要知道 为什么多线程下为什么会出现资源的不可见

简单说一下场景就是:在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改了共享变量的值后,另一个线程不能直接看到该线程修改后的变量的最新值。

首先需要了解一下 虚拟机的 java 内存模型
在这里插入图片描述

下面讲一个实例
在这里插入图片描述
在这里插入图片描述

上图 定义了一个线程,该线程的任务是,休眠了一秒之后,修改了flag 的标志。而主线程要做的是拿到该对象的 flag值来输出一段话,会出现的结果就是,死循环永远无法拿到flag 为true 的状态,而无法输出打印语句:主线程进入循环执行~~~~

为什么会这样呢?
在这里插入图片描述
根据jmm,java内存模型的条件可知,所有的共享变量都会放到主内存当中,而线程所需要的变量会从主内存复制一个副本到自己的工作内存当中,这样就会导致,子线程和主线程读到的flag都是初始值false没有问题,但是在子线程已经完成修改之后,由jmm还可知,线程首先修改的是自己的工作内存,然后才会同步到主内存当中,而此时的主线程的工作内存并没有更新,读取的还是 flag=false(这里主线程没有更新工作内存的原因是:jmm会有一个优化机制,就是当使用了工作内存中的一个值多次之后,发现主内存该值一直没有变化,那么就不会再去主内存查看该值是否需要更新了)。

在这里插入图片描述
在这里插入图片描述

多线程下共享变量的可见性问题如何解决

1、加锁 synchronized:
2、对共享的变量使用volatile来修饰

1、加锁
在这里插入图片描述
原因:
在这里插入图片描述
这里的解释很清晰,就是每次进入到synchronized的代码块之后,该线程下的工作内存都会被清空,而被清空后就不得不从主内存获取最新的值最为工作内存,此时就完成了可见性问题。

2、使用volatile修饰共享变量
在这里插入图片描述
在这里插入图片描述
原因:
在这里插入图片描述
上图对可见性的解释很清晰,就是说某一个线程修改了副本当中的公共变量之后,会对主内存的值进行修改,同时会让其他线程的副本的改值失效,而不得不重新从主线程中获取最新值。

不保证原子性:

在这里插入图片描述

在这里插入图片描述
问题分析
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
即使是使用了volatile来修饰公共变量也没办法改变上述结果
在这里插入图片描述

原子性的问题解决

在这里插入图片描述

1、加锁
在这里插入图片描述
2、使用原子类

在这里插入图片描述
在这里插入图片描述

指令重排

在这里插入图片描述
示例
在这里插入图片描述
a、b、i、j的默认值都为0,分别执行上述两个线程,并打印出i和j的值

在这里插入图片描述
上述的结果都是线程并发正常可能会出现的

但是什么情况下会出现 i和j为0的情况,是因为编译器出现了指令重排的现象,就是颠倒了线程任务当中原有的顺序,变成了i=b;a=1;和j=a;b=1,此时就会出现i=0和j=0.

问题解决
在这里插入图片描述
把四个值都使用volatile 来修饰,实现了禁止指令重排序以及其带来的安全性的问题。

volatile内存语义

在这里插入图片描述
示例
在这里插入图片描述
如上图所示,公共变量b 被volatile修饰了,那么此时当完成 b=a的操作后,那么a=3和abc=100都是可见的。
在这里插入图片描述
结论
在这里插入图片描述

volatile的使用情景

1、纯赋值操作
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

因为赋值自身是携带原子性的,所以弥补了volatile的不足,因为可以满足线程安全。

2、触发器
这个特性利用了上述讲到的 happen-before 的原则
在这里插入图片描述
在这里插入图片描述
主线程来执行两个线程的读写,结论就是如果 flag为true,那么写的操作就都实现了 a=100,b=2–,c=300,
在这里插入图片描述

volatile和synchronized的区别

在这里插入图片描述

双重检查机制 实现懒汉式单例

在这里插入图片描述
在这里插入图片描述
上图中 为什么需要给 单例的声明变量增加volatile 的修饰呢?
在这里插入图片描述
正常执行顺序如下
在这里插入图片描述
但是由于底层可能会出现重排序导致a、b、c三个步骤顺序出现问题
在这里插入图片描述
就是说如果INSTANCE没有使用volatile修饰,那么可能会出现a、c、b的现象,此时引用中有数据,但是并没有初始化实例,而图中的线程c经过判断返现引用不是null,则返回一个空对象出去,将可能在后续调用该对象的属性时,出现对象空指针问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值