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

Java中基于CAS的原子类盘点

时间:2023-03-21 23:01:59 科技观察

前言JDK提供了一系列基于CAS的原子类。CAS的全称是Compare-And-Swap,底层是lockcmpxchg指令,可以在单核和多核CPU下保证比较和交换的原子性。因此,这些原子类是线程安全的,而且是无锁并发,线程不会频繁切换上下文,所以在某些场景下性能优于加锁。本文对JDK中的原子类进行了盘点,方便我们后面使用。基本原子类AtomicInteger:Integer整数类型的原子操作类AtomicBoolean:Boolean类型的原子操作类AtomicLong:Long类型的原子操作类这里用AtomicInteger来说明其API和用法。构造方法:publicAtomicInteger():初始化一个原子整数,默认值为0AtomicIntegerpublicfinalintgetAndIncrement():原子方式将当前值加1,并返回自增前的值publicfinalintincrementAndGet():原子方式将当前值加1,并返回值自增后publicfinalintgetAndSet(intvalue):原子设置为newValue的值,并返回旧值publicfinalintaddAndGet(intdata):原子地将输入值与实例中的值相加并返回使用:结果为1000,大致表示并发情况下,保证线程安全原理分析:整体实现思路:自旋(循环)+CAS算法当旧期望值A==内存值V可以修改ied此时,当旧的期望值A!=时,将V改为B此时内存值V不可修改,可以重新获取最新值。重新获取的动作是自旋publicfinalintgetAndIncrement(){returnunsafe.getAndAddInt(this,valueOffset,1);}valueOffset:偏移量表示变量值相对于当前对象地址的偏移量,Unsafe是根据内存偏移地址获取从主内存复制到工作内存的值(最新的值必须从mainmemory每次到本地内存),然后执行compareAndSwapInt()并与主内存的值进行比较。如果方法返回false,则执行while方法,直到期望值与真实值相同,然后修改数据。原子类AtomicInteger的value属性是volatile类型,保证多线程间的内存可见性,防止线程从工作缓存中获取无效变量。原子引用原子引用主要是对对象的原子操作。原子引用分为AtomicReference、AtomicStampedReference和AtomicMarkableReference。它们之间有什么区别?AtomicReference类是一个普通的原子类对象publicclassAtomicReferenceDemo{publicstaticvoidmain(String[]args){Useruser1=newUser("旭阳");//创建一个原子引用包装类AtomicReferenceatomicReference=newAtomicReference<>(user1);while(true){Useruser2=newUser("alvin");//比较和交换if(atomicReference.compareAndSet(user1,user2)){break;}}System.out.println(atomicReference.get());}}@Data@AllArgsConstructor@ToStringclassUser{privateStringname;}调用compareAndSet()方法比较替换对象ABA问题但是如果使用AtomicReference类,就会出现ABA问题。这意味着什么?也就是一个线程把共享变量从A改成B,再改回A。这就是另一个线程无法感知变化过程,只是傻傻地比较,以为没有变化,结果是仍然是A开头,并替换它。向上。事实上,只要共享变量发生变化,CAS就会失败,这一点是确实存在的。可以做什么?具有版本号的AtomicStampedReference类对象@Slf4j(topic="a.AtomicStampedReferenceTest")publicclassAtomicStampedReferenceTest{//构造AtomicStampedReferencestaticAtomicStampedReferenceref=newAtomicStampedReference<>("A",0);publicstaticvoidmain(String[]args)throwsInterruptedException{log.debug("mainstart...");//获取值AStringprev=ref.getReference();//获取版本号intstamp=ref.getStamp();log.debug("version{}",stamp);//如果有其他线程干扰,就会出现ABA现象other();线程.睡眠(1000);//尝试更改为Clog.debug("changeA->C{}",ref.compareAndSet(prev,"C",stamp,stamp+1));}privatestaticvoidother()throwsInterruptedException{newThread(()->{log.debug("更改A->B{}",ref.compareAndSet(ref.getReference(),"B",ref.getStamp(),参考getStamp()+1));log.debug("更新版本为{}",ref.getStamp());},"t1").start();线程.睡眠(500);newThread(()->{log.debug("更改B->A{}",ref.compareAndSet(ref.getReference(),"A",ref.getStamp(),ref.getStamp()+1));log.debug("更新版本为{}",ref.getStamp());},"t2").start();}}虽然对象的值变回了A,但是由于版本变更导致主线程CAS失败。AtomicMarkableReference类其实有时候并不关心共享变量修改了几次,但是只要标记下有变化,加标记就可以了,所以就有了AtomicMarkableReference类@Slf4j(topic="c.AtomicMarkableReferenceTest")publicclassAtomicMarkableReferenceTest{//构造AtomicMarkableReference,初始标记为falsestaticstaticAtomicMarkableReferenceref=newAtomicMarkableReference<>("A",false);publicstaticvoidmain(String[]args)throwsInterruptedException{log.debug("mainstart...");其他();线程.睡眠(1000);//查看是否有变化log.debug("change{}",ref.isMarked());}privatestaticvoidother()throwsInterruptedException{newThread(()->{log.debug("更改A->B{}",ref.compareAndSet(ref.getReference(),"B",false,true));},"t1").start();线程.睡眠(500);newThread(()->{log.debug("更改B->A{}",ref.compareAndSet(ref.getReference(),"A",true,true));},"t2").start();}}通过调用isMarked()方法检查是否有变化。原子数组AtomicIntegerArray:Integer类型的原子数组AtomicLongArray:Long类型的原子数组AtomicReferenceArray:引用类型的原子数组直接看例子(10);Threadt1=newThread(()->{intindex;for(inti=1;i<100000;i++){index=i%10;//range0~9array.incrementAndGet(index);}});Threadt2=newThread(()->{intindex;for(inti=1;i<100000;i++){index=i%10;//range0~9array.decrementAndGet(index);}});t1.开始();t2.开始();线程.sleep(5*1000);System.out.println(array.toString());}}两个线程同时对数组对象进行加减操作,最终结果为0,说明线程安全。原子字段更新器AtomicReferenceFieldUpdaterAtomicIntegerFieldUpdaterAtomicLongFieldUpdater使用字段更新器对对象的字段(Field)进行原子操作,只能与volatile修饰的字段一起使用,否则会出现异常。@DatapublicclassAtomicReferenceFieldUpdaterTest{privatevolatileintage=10;私人intage2;publicstaticvoidmain(String[]args){AtomicIntegerFieldUpdaterintegerFieldUpdater=AtomicIntegerFieldUpdater.newUpdater(AtomicReferenceFieldUpdaterTest.class,"age");AtomicReferenceFieldUpdaterTestref=newAtomicReferenceFieldUpdaterTest();//+1用于可变年龄字段integerFieldUpdater.getAndIncrement(ref);System.out.println(ref.getAge());//修改非易失性age2integerFieldUpdater=AtomicIntegerFieldUpdater.newUpdater(AtomicReferenceFieldUpdaterTest.class,"age2");integerFieldUpdater.getAndIncrement(ref);}}原子字段更新器只能更新volatile字段,可以保证可见性,不能保证原子性。原子累加器原子累加器主要用于累加,相关类有LongAdder、DoubleAdder、LongAccumulator、DoubleAccumulator。LongAdder是jdk1.8引入的,性能优于AtomicLong。LongAddr类是LongAccumulator类的特例,但LongAccumulator提供了更强大的功能,可以自定义累加规则。当accumulatorFunction为null时,相当于LongAddr。这是一个性能比较示例。publicclassLongAdderTest{publicstaticvoidmain(String[]args){System.out.println("LongAdder......");for(inti=0;i<5;i++){addFunc(()->newLongAdder(),adder->adder.increment());}System.out.println("AtomicLong......");for(inti=0;i<5;i++){addFunc(()->newAtomicLong(),adder->adder.getAndIncrement());}}privatestaticvoidaddFunc(SupplieradderSupplier,Consumeraction){Tadder=adderSupplier.get();长启动=System.nanoTime();列表<线程>ts=newArrayList<>();//40个线程,每人累加50万for(inti=0;i<40;i++){ts.add(newThread(()->{for(intj=0;j<500000;j++){action.accept(adder);}}));}ts.forEach(t->t.start());ts.forEach(t->{try{t.join();}catch(InterruptedExceptione){e.printStackTrace();}});长端=System.nanoTime();System.out.println(adder+"cost:"+(end-start)/1000_000);}}主要是因为LongAdder会设置多个累加单元,Therad-0累加Cell[0],Thread-1累加Cell[1]...最后将Results进行聚合,使得累加时对不同的Cell变量进行操作,从而减少CAS重试失败,从而提高性能。小结本文总结了JDK中提供的各种原子类,包括基本原子类、原子引用类、原子数组类、原子字段更新器和原子累加器。有时候,使用这些原子类比加锁的性能更高,尤其是在读多写少的场景下。不过,不知道大家有没有发现,对于一个共享变量,所有的原子操作都是原子的。如果操作多个共享变量,循环CAS无法保证操作的原子性,还是老老实实加锁吧。

最新推荐
猜你喜欢