为什么volatile保证不了线程安全
首先要知道,想要线程安全必须保证 原子性,可见性,有序性。
首先要了解的是, Volatile
禁止指令重排序(有序性),保证内存可见性问题,对 变量单个操作保证原子性, 那么它为什么不能保证线程安全呢?
💡 下面首先会单独介绍什么是对变量单个操作保证原子性
概念普及
JMM规定了内存主要划分为主内存和工作内存两种。每个线程都有一个自己的工作内存, 对变量的操作都是在缓存中进行的, 然后再将修改后的值返回到主存中,Java内存模型规定了所有的变量都存储在主内存(Main Memory)中 ,每条线程 还有自己的工作内存(Working Memory),线程的工作内存中保存了被该线程使用的变量的主内存副本,线程对变量的所有操作(读取、赋值等)都必须在工作内存中进行,而不能直接读写主内存中的数据。不同的线程之间也无法直接访问对方工作内存中的变 量,线程间变量值的传递均需要通过主内存来完成,线程、主内存、工作内存三者的交互关系如图:
一个变量如何从主内存拷贝到工作内存、如何从 工作内存同步回主内存这一类的实现细节,Java内存模型中定义了以下8种操作来完成:
举个小小的例子:
如果我们在代码中对一个对象重新赋值,int race = 0
,那么随后就要将新的值同步到当前线程的工作内存,然后再从工作内存同步到主内存,这中间会经历Java内存模型中定义的三种操作use→assign→write
,然后可能会触发缓存一致性协议,使其他线程工作内存中的 race 变量全部失效,其他线程下次获取 race 变量值,都要从主内存中重新获取最新值。
但是,对于未使用volatile 修饰的变量而言,use→assign→write
,这三个操作并不是原子性的,这也就意味着其并不是线程安全的。假设有thread-1和thread-2两个线程,thread-1和thread-2工作内存中保留着 race 副本,thread-1对 race 进行了重新赋值,但是其最新值还未同步到主内存,此时thread-2线程读取 race 变量,那么就会直接命中其工作内存获取一个旧数据。
而 volatile 变量将read load use 三个原子操作变成一个原子操作;将assi