格言:在程序猿界混出点名堂!
《JAVA并发编程实战》解读
【连载】第3章-3.1.2-4可见性
回顾:上一节聊了共享变量,如果多线程共有,可能会导致失效的风险,增加synchronized保护后,可以避免风险的发生,这一节聊一聊可见性的其他问题和解决方式。
非原子的64位操作
Java内存模型要求,变量的读取操作和写入操作都必须是原子操作,比如int、byte、char等,但是对于非volatile修饰的long和double变量,JVM允许将64位的读操作或者写操作分为两个32的操作。
比如,当去读一个非volatile类型的long的变量,如果对该变量的读操作和写操作在不同的线程中执行,那么可能读取到某个值的高32位和另外一个值的低32位。
因此,为了保证64位的变量的原子操作,需要用volatile修饰或者用synchronized修饰。如下手绘图:
Volatile
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。
而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。
Volatile的使用场景
比如JDK中AtomicLong,采用volatile+CAS,来保证线程安全性,比如仅使用CAS是否能保证线程安全?肯定不可以。因为如果只用CAS,如果多线程访问时,知识对CPU cache更新,其他线程并不可见,所以保证不了线程安全,同时,long是64位,用volatile修饰保证了long的操作原子性。
平时用的比较多的场景,比如状态的更新,如下:
volatile boolean asleep;
...
while(!asleep){
doSomething();
}
思考
用Volatile修饰的变量vi,多线程执行vi++,线程安全吗?
添加微信公众号,关注下期答案。
喜欢连载可关注
简书
或者微信公众号
:
简书专题:Java并发编程实战-可爱猪猪解读
https://www.jianshu.com/c/ac717321a386
微信公众号:可爱猪猪聊程序
。