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

什么是ABA问题?Java中的本机解决方案是什么?原理是什么?

时间:2023-03-12 13:35:46 科技观察

AtomicStampedReference是一个带有时间戳的对象引用,可以很好的解决CAS机制中的ABA问题。本文将通过一个案例对其进行介绍和分析。1、ABA问题ABA问题是CAS机制中的一个问题,他的描述如下。直接画个图来演示一下,什么意思呢?也就是说,一个线程把数据A变成B,然后又变成A。这时,另一个线程读取时,发现A没有变化,误以为是原来的A,这就是著名的ABA问题。ABA问题的后果是什么?让我们举个例子。小偷从别人家里偷钱然后还回去。还是原来的钱?ABA问题是一样的。如果解决不好,会带来很多问题。最常见的问题就是资金问题,就是有人挪用了你的钱,还没等你发现就退了。但其他人违反了法律。解决这个ABA问题的方法就是用今天所谓的AtomicStampedReference。2.AtomicStampedReference1。解题先举个ABA的例子,重现ABA问题的场景。publicclassAtomicTest{privatestaticAtomicIntegerindex=newAtomicInteger(10);publicstaticvoidmain(String[]args){newThread(()->{index.compareAndSet(10,11);index.compareAndSet(11,10);System.out.println(线程。currentThread().getName()+":10->11->10");},"张三").start();newThread(()->{try{TimeUnit.SECONDS.sleep(2);booleanisSuccess=index.compareAndSet(10,12);System.out.println(Thread.currentThread().getName()+":index为预期的10,"+isSuccess+"设置的新值为:"+index.get());}catch(InterruptedExceptione){e.printStackTrace();}},"李四").start();}}上面代码中,我们使用了张三线程,对于index10->11->10变化,然后里丝线程读取索引看是否有变化,并设置一个新值。运行看看结果:本案例复现了ABA的问题场景。让我们看看如何使用AtomicStampedReference来解决这个问题。publicclassAtomicTest2{privatestaticAtomicIntegerindex=newAtomicInteger(10);staticAtomicStampedReferencestampRef=newAtomicStampedReference(10,1);publicstaticvoidmain(String[]args){newThread(()->{intstamp=stampRef.getStamp();System.out.println(Thread.currentThread().getName()+"第1次版本号:"+stamp);stampRef.compareAndSet(10,11,stampRef.getStamp(),stampRef.getStamp()+1);System.out.println(Thread.currentThread().getName()+"第2次版本号:"+stampRef.getStamp());stampRef.compareAndSet(11,10,stampRef.getStamp(),stampRef.getStamp()+1);System.out.println(Thread.currentThread().getName()+"第三次版本号:"+stampRef.getStamp());},"张三").start();newThread(()->{try{intstamp=stampRef.getStamp();System.out.println(Thread.currentThread().getName()+"第1次版本号:"+stamp);TimeUnit.SECONDS.sleep(2);booleanisSuccess=stampRef.compareAndSet(10,12,stampRef.getStamp(),stampRef.getStamp()+1);System.out.println(Thread.currentThread().getName()+"是否修改成功:"+isSuccess+"当前版本:"+stampRef.getStamp());System.out.println(Thread.currentThread().getName()+"当前实际value:"+stampRef.getReference());}catch(InterruptedExceptione){e.printStackTrace();}},"李四").start();}}我们再分析一下上面的代码,我们会发现AtomicStampedReference增加了一个时间戳,也就是说每次修改只需要设置不同的版本即可。我们先运行一下看看:这里我们使用了AtomicStampedReference的compareAndSet函数,它有四个参数:compareAndSet(VexpectedReference,VnewReference,intexpectedStamp,intnewStamp)。(1)第一个参数expectedReference:表示期望值。(2)第二个参数newReference:表示要更新的值。(3)第三个参数expectedStamp:表示期望的时间戳。(4)第四个参数newStamp:表示要更新的时间戳。这个compareAndSet方法是如何实现的,我们深入源码看看。2.源码分析publicbooleancompareAndSet(VexpectedReference,VnewReference,intexpectedStamp,intnewStamp){Paircurrent=pair;returnexpectedReference==current.reference&&expectedStamp==current.stamp&&((newReference==current.reference&&newStamp==current.stamp)||cas??Pair(current,Pair.of(newReference,newStamp)));}这四个参数的含义刚才已经说了,我们主要关心的是实现,首先我们看到这个Pair,所以我们要图出来了,我们看看这个Pair是什么,privatestaticclassPair{finalTreference;finalintstamp;privatePair(Treference,intstamp){this.reference=reference;this.stamp=stamp;}staticPairof(Treference,intstamp){returnnewPair(reference,stamp);}}这里我们会发现Pair只包含值引用和时间戳戳。在compareAndSet方法中,最后调用了casPair方法。从名字就可以看出,它主要是利用CAS机制来更新新的值引用和时间戳。我们可以进入这个方法看看。//底层调用UNSAFE的compareAndSwapObject方法privatebooleancasPair(Paircmp,Pairval){returnUNSAFE.compareAndSwapObject(this,pairOffset,cmp,val);}3.总结其实除了AtomicStampedReference类,还有一个原子类也可以解决,就是AtomicMarkableReference,它不维护一个版本号,而是维护一个boolean类型的标记,其用法不如AtomicStampedReference灵活。因此,它只在特定场景下使用。本文转载自微信公众号“愚公要移山”,可关注下方二维码。转载本文请联系愚公移山公众号。