voliate关键字的应用误区

写下这篇博客也是因为本人之前对voliate关键字理解不透彻,才有了应用误区,希望同样没有理解到位的朋友可以一起踩坑,也欢迎上帝视角明明白白的大佬指出本文的不当之处。

先说一下自己之前对voliate的理解,voliate通过内存屏障可以禁止指令重排序和保证可见性,但是不能保证并发安全。禁止指令重排序就不说了,主要说一说如何保证可见性以及为什么不能保证并发安全。

首先理解一下一个线程如何去修改变量a的值,有三步:

第一步从主内存中读取a的值到该线程的工作内存。

第二步在工作内存中修改a。

第三步将a的值刷回主内存。

被voliate修饰的变量在进行写操作时,处理器会多一条Lock指令,Lock指令会将修改后的值直接写进主内存。同时还会让其他线程的工作内存中缓存的a的值失效,这两点就可以保证了可见性,我理解的可见性是指所有线程修改a之前a的值都和主内存中一样,都是一致的。

举个栗子如果有两个thread同时操作voliate修饰变量a,thread1和thread2都将a的值从主内存中读取到工作内存中之后,thread2对a做了修改之后会使thread1工作内存中a的值失效,同时及时地将a的值刷新进主内存,这时候thread1想去修改a发现自己工作内存中的值失效了,就会去主内存中重新读取,这样就避免了典型的并发问题。日常voliate的应用场景中喜欢用来修饰全局变量,曾经以为这样就可以避免该全局变量并发问题,其实不是的。

因为voliate不能将一个非原子性的操作变为原子性。所谓原子性的操作是指该操作在执行期间不受其他操作影响。上面这个对例子中对变量a的值做修改这就不是一个原子性操作,因为这个操作有三步。

还是刚刚那个例子加深一下理解。thread1如果在第一步和第二步中间a的值被thread2修改了,这时候thread1应该在thread2修改之后的a的基础上再做修改,总之thread1的操作结果就要受其他线程影响了。重点来了!voliate可以保证thread1的操作结果正确,但是它不能阻止thread2去影响thread1的操作结果。这就是voliate不能将一个非原子性的操作变为原子性,但是加锁(该变量只能同时被一个线程操作)或者CAS无锁可以,这是题外话。

说了这么多还是没有说为什么voliate不能保证并发安全。先看个情景,还是上面这个例子,刚说thread1在第一步和第二步之间的时候thread2把a修改了,这时候voliate棒棒的一系列骚操作让thread1在执行第二步真正去修改a的时候拿到的还是最新的即thread2修改之后刷新回主内存的值。但是如果thread1在第二步和第三步之间的时候thread2把a修改了呢!这时候thread1已经把a修改完了,已经准备刷回主内存了,这里大家不要和我一样钻牛角尖,之前我也在想,thread2把a修改了刷回主内存那thread1工作内存里a的值不就失效了?thread1已经修改完了就不会再去主内存中读a的值再修改一遍了,它现在要做的是把刚刚修改好a的值也刷回主内存,哦吼,这样一刷就出问题了。这就是voliate不能保证原子性也就是为什么不能保证并发安全的原因。

你可能感兴趣的:(日常问题)