原子操作:一个或多个操作在 CPU 执行过程中不被中断的特性
当我们说原子操作时,需要分清楚针对的是 GPU指令级别还是高级语言级别
什么是原子操作
比如:经典的银行转账场景,是语言级别的原子操作;
而当我们说 volatile修饰的变量的复合操作,其原子性不能被保证,指的是CPU 指令级别。
二者的本质是一致的。
“原子操作”的实质其实并不是指“不可分割”,这只是外在表现,本质在于多个资源之间有一致性的要求,操作的中间态对外不可见。
比如:在 32 位机器上写 64 位的 long 变量有中间状态(只写了 64 位中的32位);银行转账操作中也有中间状态(A 向B 转账,A 扣钱了,B 还没来得及加钱)
原子操作的实现方式
Java 使用锁和自旋 CAS 实现原子操作
并发包中提供了很多常用的原子类来支持原子操作:
1、AtomicInteger
2、AtomicLong
3、AtomicBoolean
4、AtomicReference
5、LongAdder
CAS 是并发包的基石,但用 CAS有三个问题:
1、ABA 问题
根源:CAS 的本质是对变量的 current value,期望值 expected value 进行比较,二者相等时,再将给定值given update value 设为当前值。
因此会存在一种场景,变量值原来是 A,变成了 B,又变成了 A,使用 CAS 检查时会发现值并未变化,实际上是变化了。
对于数值类型的变量,比如 int,这种问题关系不大,但对于引用类型,则会产生很大影响。
ABA问题解决思路:版本号。在变量前加版本号,每次变量更新时将版本号加1,A -> B -> A,就变成 1A ->2B ->3A。
JDK5 之后Atomic包中提供了AtomicStampedReference.compareAndSet() 来解决ABA问题。
2、循环时间长则开销大
自旋CAS 若长时间不成功,会对 CPU 造成较大开销。不过有的 JVM可支持 CPU的 pause 指令的话,效率可有一定提升。
pause 作用:
延迟流水线指令(de-pipoline),使 CPU 不至于消耗过多执行资源。
可避免退出循环时因内存顺序冲突(memorey order violation )引起CPU流水线被清空(CPU pipeline flush),从而提高 CPU 的执行效率。
3、只能保证一个共享变量的原子操作
CAS 只能对单个共享变量如是操作,对多个共享变量操作时则无法保证原子性,此时可以用锁。
另外,也可“取巧”,将多个共享变量合成一个共享变量来操作。比如a=2,b=t,合并起来 ab=2t,然后用 CAS 操作 ab.
JDK5 提供 AtomicReference 保证引用对象间的原子性,它可将多个变量放在一个对象中来进行 CAS 操作。
原子类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
原子数组:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
原子属性更新器:AtomicLongFieldUpdater、AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
解决 ABA 问题的原子类:AtomicMarkableReference(通过引入一个booolean来反映中间有没有变过),AtomicStampedReference(通过引入一个int累加来反映中间有没有变过)

Was this helpful?

0 / 0

发表回复 0

Your email address will not be published.