当前位置: 首页 > 科技观察

从JUC源码看CAS,做个笔记...

时间:2023-03-15 13:38:50 科技观察

前言》CAS在JUC包中被广泛使用,在工作和面试中经常会遇到CAS,包括乐观锁,这是不可避免的对CAS的思考,CAS是什么?”1.什么是CAS?说到CAS,大家基本都会想到乐观锁、AtomicInteger、Unsafe……当然,你也可能什么都想不起来!不管你怎么想,我的第一印象就是一把乐观锁。毕竟事务中经常会用到乐观锁来更新事务状态,所以很自然的就会想到这个SQL:updatetrans_ordersetorder_status=1whereorder_no='xxxxxxxxxxx'andorder_status=0;事实上,set和where都带有order_status。那么什么是CAS?CAS即Compare-and-Swap,即比较和替换。常用于并发算法,JUC(java.util.concurrent)包下的很多类都使用了CAS。一个很常见的问题就是多线程运行i++的问题。一般的解决办法是加上synchronized关键字修饰,当然也可以使用AtomicInteger代码如下:publicclassCasTest{privatestaticfinalCountDownLatchLATCH=newCountDownLatch(10);privatestaticintNUM_I=0;privatestaticvolatileintNUM_J=0;privatestaticfinalAtomicIntegerNUM_K=newAtomicIntegerNUM_K(staticIntubicString)(0]arpidString);throwsInterruptedException{ExecutorServicethreadPool=Executors.newFixedThreadPool(10);for(inti=0;i<10;i++){threadPool.execute(newRunnable(){publicvoidrun(){for(intj=0;j<10000;j++){NUM_I++;NUM_J++;NUM_K.incrementAndGet();}LATCH.countDown();}});}LATCH.await();System.out.println("NUM_I="+NUM_I);System.out.println("NUM_J="+NUM_J);System.out.println("NUM_K="+NUM_K.get());threadPool.shutdown();}}让我们从AtomicInteger开始理解CAS。2.源码分析publicclassAtomicIntegerextendsNumberimplementsjava.io.Serializable{privatestaticfinallongserialVersionUID=6214790243416807050L;//setuptouseUnsafe.compareAndSwapIntforupdatesprivatestaticfinalUnsafeunsafe=Unsafe.getUnsafe();privatestaticfinallongvalueOffset;static{try{valueOffset=unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));}catch(Exceptionex){thrownewError(ex);}}privatevolatileintvalue;publicfinalintincrementAndGet(){returnunsafe.getAndAddInt(this,valueOffset,1)+1;}publicfinalintdecrementAndGet(){returnunsafe.getAndAddInt(this,valueOffset,-1)-1;}}可见使用了Unsafe类下的getAndAddInt方法。Unsafe类的很多方法都是native方法,主要是硬件层面的原子操作。/***@paramvar1当前对象*@paramvar2当前对象在内存偏移处,Unsafe可以根据内存偏移地址获取数据*@paramvar4操作值*@return*/publicfinalintgetAndAddInt(Objectvar1,longvar2,intvar4){intvar5;do{//获取var1在内存中的值var5=this.getIntVolatile(var1,var2);//将var1赋值给var5+var4,赋值时会判断var1是否为var5}while(!this.compareAndSwapInt(var1,var2,var5,var5+var4));returnvar5;}//原子操作publicfinalnativebooleancompareAndSwapInt(Objectvar1,longvar2,intvar4,intvar5);至于compareAndSwapInt的分析就忽略了。看完代码,其实这个过程是:比较var1的值是否为var4,如果是,则更新var1为var5。如果不是,继续循环直到var1是var4。3.问题总结一直拿不到,不就是一直循环吗。当线程多的时候,会长时间自旋,造成资源浪费。你更新了,我给你更新回来,你还不知道。ABA问题!比如像这样,A想把值更新为a,但是还没有抢到资源。这时候B把对象更新为b,然后马上又更新回a。此时,A什么都不知道。.以乐观锁为例:--0->1updatetrans_ordersetorder_status=1whereorder_no='xxxxxxxxxxx'andorder_status=0;--1->0updatetrans_ordersetorder_status=1whereorder_no='xxxxxxxxxxx'andorder_status=0;--0->1updatetrans_ordersetorder_status=1whereorder_no='xxxxxxxx=0;解决办法可以是增加version进行版本号控制。--0->1updatetrans_ordersetorder_status=1whereorder_no='xxxxxxxxxxx'andorder_status=0andversion=0;--1->0updatetrans_ordersetorder_status=1whereorder_no='xxxxxxxxxxx'andorder_status=0andversion=1;--0->1updatetrans_ordersetorder_status=1whereorder_xx=xx_xx='usxx_xx='0和版本=0;代码中可以看到AtomicStampedReference类:/***以原子方式设置引用给定的更新值和flag的值,*如果当前引用==预期引用,当前flag==预期的标志。**@paramexpectedReference预期参考*@paramnewReference更新值*@paramexpectedStamp预期标志*@paramnewStamp更新标志*@return{@codetrue}ifsuccessful*/publicbooleancompareAndSet(VexpectedReference,VnewReference,intexpectedStamp,intnewStamp){Paircurrent=pair;returnexpectedReference==current.reference&&expectedStamp==current.stamp&&((newReference==current.reference&&newStamp==current.stamp)||casPair(current,Pair.of(newReference,newStamp)));}其实是一个额外的Flag(stamp)防止ABA问题,类似乐观锁的版本。本文转载自微信公众号“刘志航”,可通过以下二维码关注。转载本文请联系刘志航公众号。