关于java的volatile关键字吐血总结

通过几天的时间看了几十个帖子和咀嚼了《深入了解java虚拟机》原著,总结一下volatile的作用,首先java虚拟机内存模型定义了8类原子操作,lock,unlock,read,load,use,assign,store,write。原子操作就是只要执行了这条指令就一定执行完,要么不执行。java中对这些原子操作的执行顺序有约束,即这些操作在执行时是有顺序的。但是有顺序并不代表连续执行,假如两个非volatile修饰的变量a、b,两个线程J、K,单线程执行情况下使用a的原子操作顺序是 线程从主存read-a然后load-a入线程工作空间(对应硬件系统的cpu缓存区),然后线程内使用a(可能多次使用或者赋值)、最后将线程内最后一次的赋值操作后的线程工作区的a store到主存,然后把这个a write进主存中变量a的区域。顺序出来了,但是这些顺序不一定连续,假如这个线程还对变量b操作,那么可能会在read a之后不是去load a而是先去read b,其它各原子操作同样面临类似情况。对普通变量而言,线程内工作区与主存交互只会发生两次,第一次使用这个变量的时候会将主存内的变量复制进入工作区,在该变量最后一次赋值(原著的原话是一个线程不允许丢弃最近的assign操作,要把它同步会主存,那么一个线程内有多次assign操作时哪个是最近的?就是最后一次)修改后会将工作区回写主存。

如果是变量a未经过volatile修饰,对于单线程,则任意两个原子操作之间可能会穿插其它的指令,上面描述过了,每次使用(不是第一次的话)之前不会重新去读主存数据,每次赋值(不是最后一次的话)后不会回写主存。对于多线程而言,以上的任意两个原子操作之间都可能会阻塞而切换其它线程执行。

如果是变量a经过了volatile修饰,对于单线程,read,load,use操作之间不会穿插其它的指令,他们连续出现,即每次使用前会重新从主存加载变量。assign、store、write连续出现,即一旦(就是每次)工作区更改变量,则把变量同步回主存中。对于多线程而言,以上的任意两个原子操作之间都可能会阻塞而切换其它线程执行。

volatile 实现可见性,都说volatile能实现变量可见性,什么鬼?首先可见性是指一个线程修改之后其它线程立即会知道这个变量在主存被修改了,注意,是知道,而不是立即会取得。什么时候取得?就是进行读主存操作read,什么时候read,在每次使用(use)前,为什么volatile变量在线程使用前要重新去读主存,因为volatile变量在某个线程工作区回写主存的write操作时会有个锁前缀(一种内存屏障),其作用就是在这个线程把变量回写主存后立即通知其它线程这个变量在主存被修改了,其它线程在之前从主存读取的线程内工作区(线程内cpu缓存)变量副本无效了,根据cpu缓存一致性协议,这个无效的变量在下次使用前要重新从主存读取。那问题来了,在得知之读取的变量过期的线程要在下次读取才能获取之前其它线程修改这个变量后的最新值,那这两次读取之间如果有其它原子操作,比如前一次读取主存变量,然后对这个变量进行增加(对应使用和赋值原子操作),然后准备写回主存(store和write)的时候被通知这个变量在主存被其它线程修改了,变无效,但是在当前线程内也不会重读主存最新值了,因为此时没有使用操作,而是直接到了写阶段,那就会产生脏数据,这就是为什么volatile变量保证了可见性,但是不能保证线程安全。所以不管变量有没有被volatile修饰,在线程并发环境中如果线程内执行的代码是包含对该变量的写操作(write,write之前必定会有store,store之前必定有assign),那就不是线程安全的。注意这里是说线程并发环境下的线程代码内(即每个线程都会执行的代码),如果有多个线程和一个主线程,把写操作放在主线程内(只有主线程会执行,其它线程不会,所以这个写操作不在线程并发环境下),其它并发线程只有读操作(仅仅使用值而不更改值),这个就是一写多读,那此时的volatile变量是线程安全的。因为只有一个线程写。那既然是一个线程负责写,其它线程负责读,那为什么要用volatile变量,普通变量也是安全的啊?这是因为有的场景就算是一写多读,但是它需要这个变量被主线程修改后其它线程能立马得知,然后根据最新值来做后续动作。比如很多子线程通过判断一个变量的值来决定是否继续运行,那如果这个变量是普通变量,那么主线程就算修改了主存中这个变量值,子线程根本都不会得知这个变量修改了,线程内每次使用的时候还是拿到自己线程工作区的变量值来比较,这个值就是在主存中最初的值,而不会使用前去重读主存获取最新值。

很多帖子包括java虚拟机原著内都举volatile修饰的变量自增在线程并发环境下来说明volatile变量不保证线程安全,它们都把这个操作分解成了3个操作,这里的3个操作不是原子类操作,原子类操作会超过3个。

总之volatile的作用就是一个线程把变量写入主存后其它线程会立即被通知这个变化,知道这个变化。如果A、B线程都在对volatile变量t运算,在A运算完自己工作区的t(已经经过使用操作了)之后被通知主存中的t被B修改了,那A中t也不会变为B修改后的t,那A将运算结果回写主存的t就是脏数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值