volatile为什么不保证原子性?为什么Atomic保证原子性?
volatile
前言
大家都知道,volatile是具有可见性的。即是在多线程操作下,volatile修饰的共享变量会在更新操作之后,每个线程获得最新的值。
不了解volatile的实现原理可以参考:博客链接
那么问题来了,既然可以保证修改后的变量可以被立即更新,那么为什么不能保证操作的原子性呢?
举个栗子
private volatile int i = 0;
i++;
如果在多线程操作下,i++调用10次,那么i的值会是10吗?答案是小于10的,因为会有操作被覆盖。
原因
i++的操作是分三步实现的。
- 线程获取 i 的值
- temp = i+1
- i = temp(写回主存)
错误理解 (不想看就跳过)
如果此时i的值是5,此时线程A获取了 i 的值,并且执行到了第二步。然后线程A阻塞,轮到线程B执行。B执行同样的操作,获取到 i = 5,然后执行自增操作并且写回主存(此时i = 6)。然后轮到线程A执行将自增完成的 i 值(i = 6)写回主存。
这样,虽然进行了两次自增,但是 i 的值只增加了一次,不具备原子性。
( 更新完变量后写回主存不是立即执行的,这个时候如果对volatile变量执行操作,保证不了原子性)。虽然我很想这样说,但是这个是错误的理解
因为assign store write这三个原子指令是连续的。也就是说,volatile修饰的变量被修改完成之后,会被立即写回主存,其他变量可以立即看到新值。(图片来自尚硅谷)
真正原因
根据缓存一致性,一个处理器的缓存回写到主存会导致其他处理器的缓存失效。当线程A,B同时执行volatile int i的自增的时候,先执行完的线程A回写数据到主存,导致B的缓存变量无效(因此执行+1操作已经没有意义),直接从主存读取最新的值,这样就相当于少做了一次+1。
自称从美团并发构架师的人那学来的无敌图(但是被我偷了)@博主void丿
解决措施
1.使用总线锁保证原子性
所谓总线锁就是使用处理器提供一个LOCK#信号,当一个处理器在总线输出此信号的时候,其他处理器将被阻塞,该处理器独共享内存
2.使用缓存锁保证原子性
在同一时刻只需要保证某个内存地址的操作是原子性即可。
3.如果是int类型 i++这样的自增操作,直接使用AtomicInteger(保证原子性)
为什么AtomicXXX保证原子性
Atomic使用了volatile修饰value,然后一直使用CAS保证原子性。(其实还用到了native)
看一下Atomicxxx的源码,这里是AtomicLong
public class AtomicLongextends Numberimplements java.io.Serializable {
private volatile long value;
/**
* Creates a new AtomicLong with the given initial value.
*
* @param initialValue the initial value
*/
public AtomicLong(long initialValue) {
value = initialValue;
}
/**
* Creates a new AtomicLong with initial value {@code 0}.
*/
public AtomicLong() {
}
其中value是用volatile修饰的
CAS源码
int compare_and_swap (int* reg, int oldval,int newval)
{
ATOMIC();
int old_reg_val = *reg;
if (old_reg_val == oldval)
*reg = newval;
END_ATOMIC();
return old_reg_val;
}
CAS是乐观锁,如果发现寄存器旧值发生了改变,说明有CPU在修改,重新进行这次操作。用CAS一直判断 就可以保证随时获得最新的值,保证操作的原子性。
都看到这里了,还不点个赞支持支持吗 (ノへ ̄、)
本文地址:https://blog.csdn.net/glum_0111/article/details/107672828