Java
java.util
Arrays
HashSet
TreeSet
Deque
ArrayDeque
ArrayList
LinkedBlockingDeque
Map
HashMap
HashTable
TreeMap
LinkedHashMap
ComputeIfAbsent 在jdk8下的死锁场景
synchronized的锁升级过程
Volatile 关键字
redis 中的Lua脚本
AQS - 从干饭角度解析
ConcurrentHashMap
本文档使用 MrDoc 发布
-
+
首页
Volatile 关键字
## 1. 前言 Volatile 是一个经常用于多线程并发下的关键字,作用是标记某个变量,让其多个线程并发读写时必须取最新的值。理解volatile关键字,先要理解内存交互操作。 ## 2. 内存间交互操作 JVM 规定了以下8种操作是原子性的(因为long和double类型的非原子性协定,以下只针对32位的基础类型)。作为使用者一般只要用**先行发生(Happens-Before)原则**,思考下面加粗的内容即可。即如果A发生的操作能被B观察到,且指令顺序不在JVM规定的先行发生规则时,就有可能发生指令重排而线程不安全。 - **lock(锁定)** - **unlock(解锁)** - **read(读取)** - load(载入) - use(使用) - assign(赋值) - store(存储) - **write(写入)** ## 3. Volatitle特性 一般来说有两个特性:1. 对所有线程的可见性;2. 禁止指令重排优化。 ### 3.1 对所有线程的可见性 一个线程修改了 Volatitle变量后,能将影响立即同步到其他线程。其做法是比较容易理解的,线程的私有变量是放在栈帧里不让访问的(子内存区),共享变量则是栈帧里保留了共享变量的引用,读写时再去主内存区(堆或者直接内存)里读写。那么在读Volatitle变量时,必须先从主内存区load载入然后**立即**read读取。写Volatitle变量时,在write写值后**立即**store存回主内存区。 这里的关键就是立即,两个原子性的操作组成了一个新的原子操作(load-read、write-store),期间不允许干其他能影响该值的事情,以此保证读时总是读到最新值,写时立即能影响到其他线程。 这里需要注意,Volatitle在仅有单纯读和单纯写时是线程安全的,在做读写计算操作时并不是线程安全的。这是因为java的运算操作符不是原子操作。 ``` public class Test{ public static volatile int sum = 0; public static void increase() { sum++; } public static void main(String[] args) { Thread[] threads = new Thread[10]; for(int i = 0; i < 10; i++) { threads[i] = new Thread(new Runnable() { @Override public void run(){ for(int i = 0; i < 10000; i++) { increase(); } System.out.println("Thread now = " + sum); } }); threads[i].start(); } while(Thread.activeCount() > 1) { Thread.yield(); } System.out.println(sum); } } ``` 预计应该是10^6,但一般都是小于该值,《深入了解JAVA虚拟机》里解释了这个问题,从字节码上看,++指令会有一个将变量取至操作栈顶再加一赋值的操作,取栈顶时数据无误,但进行此时已经是子内存了,接下来加一和赋值时,主内存值可能已经修改,子内存和主内存不同步,故写回主内存时数据子内存已经是过期数据。 ### 3.2 禁止指令重排优化 因为read是原子的,write是原子的,单一线程内指令串行的,故保证有序。指令重排时会对所有线程的指令进行重新排序以优化执行效率,这个是机器级别的优化,故线程看自己时有序的,看其他线程就是乱序的,做为线程自身没法保证所有线程对volatile的变量的操作有序。 于是只能有JVM这个老大哥出面进行协调了,具体的做法是在读写的赋值前,JVM会插入一条`lock addl $0x0,(%esp)`之类的指令,含义为对esp寄存器的值加0并写入缓存。因为lock不允许和专门的nop空指令配合使用,故用这种无意义的操作来替代空操作,同时用lock将缓存值写入内存中(stroe-write),构成了一个内存屏障(Memory Barrier 或 Memory Fence)。内存屏障告诉操作系统,在重排序时不允许将后面的指令排到内存屏障的前面,也就是只能在两个内存屏障间进行指令重排优化,这样就保证了屏障前的volatile变量修改值能立刻影响到所有处理器和线程。 > 参考资料:《深入理解java虚拟机》
寒烟濡雨
2021年9月6日 01:22
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
关于 MrDoc
觅思文档MrDoc
是
州的先生
开发并开源的在线文档系统,其适合作为个人和小型团队的云笔记、文档和知识库管理工具。
如果觅思文档给你或你的团队带来了帮助,欢迎对作者进行一些打赏捐助,这将有力支持作者持续投入精力更新和维护觅思文档,感谢你的捐助!
>>>捐助鸣谢列表
微信
支付宝
QQ
PayPal
Markdown文件
分享
链接
类型
密码
更新密码