【Java多线程】Volatile关键字详解
来源:互联网 发布:0耗材空气净化器 知乎 编辑:程序博客网 时间:2024/05/22 05:13
预备知识
1..内存模型
通过缓存一致性协议来保证一致性。最出名的就是Intel 的MESI协议,MESI协议保证了每个缓存中使用的共享变量的副本是一致的。它核心的思想是:当CPU写数据时,如果发现操作的变量是共享变量,即在其他CPU中也存在该变量的副本,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取。
图解:
2.原子性、可见性与有序性
原子性:在Java中,对基本数据类型的变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行,要么不执行。
可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
有序性:在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
3.happens-before原则(先行发生原则)
程序顺序规则:一个线程中的每个操作,先行发生于该线程中的任意后序操作。
监视器锁规则:对一个锁的解锁,先行发生于随后对这个锁的加锁
volatile变量规则:对一个volatile域的写,先行发生于任意后续对这个volatile域的读。
传递性:如果A先行发生于B,而B又先行发生于C,那么A先行发生于C
start()规则:Thread对象的start()方法先行发生于此线程的每个一个动作
join()规则:如果线程A执行操作ThreadB.join()方法并成功返回,那么线程B中的任意操作先行发生于线程A从ThreadB.join()操作成功返回
引入
例1:没有Volatile修饰
public class VolatileTest { public static void main(String[] args) { ThreadDemo td = new ThreadDemo(); new Thread(td).start(); while(true){ if(td.isFlag()){ System.out.println("已跳出循环!"); break; } } }}class ThreadDemo implements Runnable { private boolean flag = false; @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { } flag = true; System.out.println("flag=" + isFlag()); } public boolean isFlag() { return flag; }}
输出结果(程序一直在运行):flag=true
分析:你会产生疑问,flag=true啊,为什么没有进入到if 方法里面呢!别急,我来小小的改造一下,你看看结果!
例2:加入Volatile关键字修饰
public class VolatileTest { public static void main(String[] args) { ThreadDemo td = new ThreadDemo(); new Thread(td).start(); while(true){ if(td.isFlag()){ System.out.println("已跳出循环!"); break; } } }}class ThreadDemo implements Runnable { //用volatile关键字修饰 private volatile boolean flag = false; @Override public void run() { try { Thread.sleep(200); } catch (InterruptedException e) { } flag = true; System.out.println("flag=" + isFlag()); } public boolean isFlag() { return flag; }}
运行结果(程序已结束):
flag=true已跳出循环!
分析:你会发现结果正常了,仅仅是因为加了volatile关键字修饰吗,对,就是这样,我接下来就讲讲volatile关键字。
解析
1.特点
(1)禁止指令重排序,但不具备“互斥性”;
(2)当多个线程进行操作共享数据时,可以保证内存中的数据可见,但不能保证变量的“原子性”。
2.指令重排序的问题
(1)实例化一个对象其实可以分为三个步骤:
- 分配内存空间
- 初始化对象
- 将内存空间的地址赋值给对应的引用
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
- 分配内存空间。
- 将内存空间的地址赋值给对应的引用。
- 初始化对象
如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。
3..内存可见性问题
(1)Lock前缀指令会引起处理器缓存写到内存,线程的本地内存失效,别的线程只能从主存中读取数据。而本地内存的值会立马刷新到主存中去。
(2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效。
4.synchronized和volatile比较
(1)关键字volatile是线程同步的轻量级实现,所以volatile性能比synchronized要好并且volatile只用来修饰变量,而synchronized可以修饰方法,以及代码块
(2)多线程访问volatile不会发生阻塞,而synchronized会出现阻塞
(3)volatile可以保证数据的可见性但不能保证原子性,synchronized可以保证原子性,也可以保证可见性。
5.注意
(1) 使用volatile强制的从公共内存中读取变量的值,
(2)volatile的非原子性,在i++或i=i+1;会出现不安全
(3)在JSR-133之前的旧内存模型中,一个64位long/double型变量的读写操作可以被拆分为两个32位的读写操作执行,但从JSR-133(JDK5)开始,只允许将一个64位long/double型变量的写操作可以被拆分为两个32位的写操作执行,任意读操作必须具有原子性(即必须要在单个读事务中执行)
本人才疏学浅,若有错误,还请指出
谢谢!
- 【Java多线程】Volatile关键字详解
- Java多线程 -- volatile关键字
- java多线程--volatile关键字
- Java 多线程:volatile关键字
- java 多线程 volatile 关键字
- 【java多线程 关键字】volatile
- java volatile关键字详解
- Java volatile关键字详解
- JAVA volatile 关键字详解
- Java volatile关键字详解
- Java Volatile 关键字详解
- Java多线程的volatile关键字
- JAVA 多线程之~~volatile关键字
- java多线程之volatile关键字
- Java关键字volatile多线程变成
- java 多线程 volatile 关键字分析
- java多线程中的关键字volatile
- Java 多线程6:volatile 关键字
- MFC获取应用程序的当前工作路径,并分割绝对路径
- nginx 反向代理运行问题总结
- sshd无法发起新的连接
- github使用记录
- TCP中的URG和PSH
- 【Java多线程】Volatile关键字详解
- Python 子字符串查询以及如何简单规避转义字符的麻烦
- F1V3.0-9 微服务功能介绍
- Java三大特性之封装、继承、多态
- 关于单链表的一些面试题-进阶篇(C语言实现)
- Stm32定时器中断触发AD采样
- Spring3-多个配置文件的整合
- 关于一下重要的资料路径
- Inventor API:用C++导出3D PDF