CAS (Comper And Swap)
CAS的原理
利用现代处理器都支持的CAS指令
循环这个指令,直到成功为止
CAS的问题
-
ABA问题
有两个线程对同一个变量操作,线程1:如果变量为A,那么将变量改为B,线程2:将变量从A改为C,再改为A.假设线程2执行的速度更快,将变量修改了一边,但是线程1做compare运算时,并没有发现变量发生改变,将变量改为B
-
开销问题
如果CAS运算长期不成功,处在自旋状态,将会增加CPU的开销
-
只能保证一个共享变量的原子操作
JDK中相关的原子操作类
-
更新基本类型类:
AtomicBoolean
,AtomicInteger
,AtomicLong
int addAndGet(int delta)
:以原子方式将输入的数值与实例中的值(AtomicInteger
里的value)相加,并返回结果.boolean compareAndSet(int expect,int update)
:如果输入的数值等于预期值,则以原子方式将该值设置为输入的值.int getAndIncrement()
:以原子方式将当前值加1,注意,这里返回的是自增前的值.int getAndSet(int newValue)
:以原子方式设置为newValue的值,并返回旧值.
-
更新数组类:
AtomicIntegerArray
,AtomicLongArray
,AtomicReferenceArray
int addAndGet(int i,int delta)
:以原子方式将输入值与数组中索引i的元素相加.boolean compareAndSet(int i,int expect,int update)
:如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值.- 需要注意的是,数组value通过构造方法传递进去,然后
AtomicIntegerArray
会将当前数组复制一份,所以当AtomicIntegerArray
对内部的数组元素进行修改时,不会影响传入的数组.
-
更新引用类型:
AtomicReference
,AtomicMarkableReference
,AtomicStampedReference
原子更新基本类型的
AtomicInxteger
,只能更新一个变量,如果要原子更新多个变量,就需要使用这个原子更新引用类型提供的类.Atomic包提供了以下3个类.AtomicReference
原子更新引用类型.
AtomicStampedReference
利用版本戳的形式记录了每次改变以后的版本号,这样的话就不会存在ABA问题了.这就是
AtomicStampedReference
的解决方案.AtomicMarkableReference
跟AtomicStampedReference
差不多,AtomicStampedReference
是使用pair的int stamp作为计数器使用,AtomicMarkableReference
的pair使用的是boolean mark.AtomicStampedReference
可能关心的是动过几次,AtomicMarkableReference
关心的是有没有被人动过,方法都比较简单.AtomicMarkableReference
原子更新带有标记位的引用类型.可以原子更新一个布尔类型的标记位和引用类型.构造方法是
AtomicMarkableReference(V initialRef,booleaninitialMark)
.
使用AtomicStampedReference
解决ABA问题
val asr = AtomicStampedReference<String>("chen", 0)
fun main() {
val oldStamp = asr.stamp
val oldRef = asr.reference
println("$oldRef version - $oldStamp")
val thread1 = Thread {
println("${Thread.currentThread().name}: " +
"当前变量值:$oldRef version - $oldStamp " +
"${asr.compareAndSet(oldRef, oldRef +
" - kotlin", oldStamp, oldStamp + 1)}")
}
val thread2 = Thread {
println("${Thread.currentThread().name}: " +
"当前变量值:${asr.reference} version - ${asr.stamp} " +
"${asr.compareAndSet(asr.reference, asr.reference +
" ++ ", oldStamp, oldStamp + 1)}")
}
thread1.apply {
start()
join()
}
thread2.apply {
start()
join()
}
println("${asr.reference} version - ${asr.stamp}")
}
CAS与Synchronized的区别
Synchronized是基于悲观锁实现的,在有线程要访问同步代码块时,它首先得到锁,退出或抛出异常时必须释放锁,当线程进入和退出阻塞状态时,需要对上下文进行处理,会造成额外的内存和时间消耗.
CAS是乐观锁的一种实现方式.用于并发量不高的场景.
两种方法都无法相互取代.有各自的适用的领域