原文:https://www.cnblogs.com/dolphin0520/p/3920373.html
其中有一段说
volatile 不具有原子性。并且给了一个例子:
//代码
大家想一下这段程序的输出结果是多少?也许有些朋友认为是10000。但是事实上运行它会发现每次运行结果都不一致,都是一个小于10000的数字。
可能有的朋友就会有疑问,不对啊,上面是对变量inc进行自增操作,由于volatile保证了可见性,那么在每个线程中对inc自增完之后,在其他线程中都能看到修改后的值啊,所以有10个线程分别进行了1000次操作,那么最终inc的值应该是1000*10=10000。
这里面就有一个误区了,volatile关键字能保证可见性没有错,但是上面的程序错在没能保证原子性。可见性只能保证每次读取的是最新的值,但是volatile没办法保证对变量的操作的原子性。
在前面已经提到过,自增操作是不具备原子性的,它包括读取变量的原始值、进行加1操作、写入工作内存。那么就是说自增操作的三个子操作可能会分割开执行,就有可能导致下面这种情况出现:
假如某个时刻变量inc的值为10,
线程1对变量进行自增操作,线程1先读取了变量inc的原始值,然后线程1被阻塞了;
然后线程2对变量进行自增操作,线程2也去读取变量inc的原始值,由于线程1只是对变量inc进行读取操作,而没有对变量进行修改操作,所以不会导致线程2的工作内存中缓存变量inc的缓存行无效,所以线程2会直接去主存读取inc的值,发现inc的值时10,然后进行加1操作,并把11写入工作内存,最后写入主存。
然后线程1接着进行加1操作,由于已经读取了inc的值,注意此时在线程1的工作内存中inc的值仍然为10,所以线程1对inc进行加1操作后inc的值为11,然后将11写入工作内存,最后写入主存。
我对这一段的理解,volidate
关键字保证了变量被更新后立即被更新到主存中,并且将其他线程中的该变量副本标注为无效。这样其他线程在使用这个变量时需要重新去主存中获取最新的值。
这里要注意的时,无效后什么时候获取最新的值?下次访问相同内存地址的时候,这里的地址指的是主存中的地址。
那么再来看上面的问题:自增操作可以分解为3个操作:
- 将住内存的数据拷贝到工作内存中
- 将工作内存中的数据+1
- 将工作内存中的数据写会主内存。
如果线程1执行完第1步后阻塞,线程2执行完全部的3步,那么此时线程1内的数据已经被表明为无效了。无效后是不是立刻就获取主存中的值呢?并不是。下次访问相同内存地址的时候才获取最新的值,上面3步中第一步访问主存,第二步不访问主存,第三步访问主存。所以当线程1执行完第一步阻塞后,虽然此时数据已经无效了,但是因为第二步不访问主存,所以并没有更新工作内存的数据。所以错误就产生了。
或者大家也可以这么理解,volatile
可以保证:
- 读取该变量时,不会从工作内存读取,而是直接从主存中读取
- 更新该变量时,会将更新后的值立刻写入到主存中。