Java原子变量类原理及实例解析
这篇文章主要介绍了Java原子变量类原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
一、原子变量类简介
为何需要原子变量类
保证线程安全是Java并发编程必须要解决的重要问题。Java从原子性、可见性、有序性这三大特性入手,确保多线程的数据一致性。
确保线程安全最常见的做法是利用锁机制(Lock、sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方法或者某个代码块,那么操作必然是原子性的,线程安全的。互斥同步最主要的问题是线程阻塞和唤醒所带来的性能问题。
volatile是轻量级的锁(自然比普通锁性能要好),它保证了共享变量在多线程中的可见性,但无法保证原子性。所以,它只能在一些特定场景下使用。
为了兼顾原子性以及锁带来的性能问题,Java引入了CAS(主要体现在Unsafe类)来实现非阻塞同步(也叫乐观锁)。并基于CAS,提供了一套原子工具类。
原子变量类的作用
原子变量类比锁的粒度更细,更轻量级,并且对于在多处理器系统上实现高性能的并发代码来说是非常关键的。原子变量将发生竞争的范围缩小到单个变量上。
原子变量类相当于一种泛化的volatile变量,能够支持原子的、有条件的读/改/写操作。
原子类在内部使用CAS指令(基于硬件的支持)来实现同步。这些指令通常比锁更快。
原子变量类可以分为4组:
- 基本类型
- AtomicBoolean-布尔类型原子类
- AtomicInteger-整型原子类
- AtomicLong-长整型原子类
- 引用类型
- AtomicReference-引用类型原子类
- AtomicMarkableReference-带有标记位的引用类型原子类
- AtomicStampedReference-带有版本号的引用类型原子类
- 数组类型
- AtomicIntegerArray-整形数组原子类
- AtomicLongArray-长整型数组原子类
- AtomicReferenceArray-引用类型数组原子类
- 属性更新器类型
- AtomicIntegerFieldUpdater-整型字段的原子更新器。
- AtomicLongFieldUpdater-长整型字段的原子更新器。
- AtomicReferenceFieldUpdater-原子更新引用类型里的字段。
这里不对CAS、volatile、互斥同步做深入探讨。如果想了解更多细节,不妨参考:Java并发核心机制
二、基本类型
这一类型的原子类是针对Java基本类型进行操作。
- AtomicBoolean-布尔类型原子类
- AtomicInteger-整型原子类
- AtomicLong-长整型原子类
以上类都支持CAS,此外,AtomicInteger、AtomicLong还支持算术运算。
提示:
虽然Java只提供了AtomicBoolean、AtomicInteger、AtomicLong,但是可以模拟其他基本类型的原子变量。要想模拟其他基本类型的原子变量,可以将short或byte等类型与int类型进行转换,以及使用Float.floatToIntBits、Double.doubleToLongBits来转换浮点数。
由于AtomicBoolean、AtomicInteger、AtomicLong实现方式、使用方式都相近,所以本文仅针对AtomicInteger进行介绍。
AtomicInteger用法
publicfinalintget()//获取当前值 publicfinalintgetAndSet(intnewValue)//获取当前值,并设置新值 publicfinalintgetAndIncrement()//获取当前值,并自增 publicfinalintgetAndDecrement()//获取当前值,并自减 publicfinalintgetAndAdd(intdelta)//获取当前值,并加上预期值 booleancompareAndSet(intexpect,intupdate)//如果输入值(update)等于预期值,将该值设置为输入值 publicfinalvoidlazySet(intnewValue)//最终设置为newValue,使用lazySet设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
AtomicInteger使用示例:
publicclassAtomicIntegerDemo{ publicstaticvoidmain(String[]args)throwsInterruptedException{ ExecutorServiceexecutorService=Executors.newFixedThreadPool(5); AtomicIntegercount=newAtomicInteger(0); for(inti=0;i<1000;i++){ executorService.submit((Runnable)()->{ System.out.println(Thread.currentThread().getName()+"count="+count.get()); count.incrementAndGet(); }); } executorService.shutdown(); executorService.awaitTermination(30,TimeUnit.SECONDS); System.out.println("FinalCountis:"+count.get()); } }
AtomicInteger实现
阅读AtomicInteger源码,可以看到如下定义:
privatestaticfinalUnsafeunsafe=Unsafe.getUnsafe(); privatestaticfinallongvalueOffset; static{ try{ valueOffset=unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); }catch(Exceptionex){thrownewError(ex);} } privatevolatileintvalue;
说明:
- value-value属性使用volatile修饰,使得对value的修改在并发环境下对所有线程可见。
- valueOffset-value属性的偏移量,通过这个偏移量可以快速定位到value字段,这个是实现AtomicInteger的关键。
- unsafe-Unsafe类型的属性,它为AtomicInteger提供了CAS操作。
三、引用类型
Java数据类型分为基本数据类型和引用数据类型两大类(不了解Java数据类型划分可以参考:Java基本数据类型)。
上一节中提到了针对基本数据类型的原子类,那么如果想针对引用类型做原子操作怎么办?Java也提供了相关的原子类:
- AtomicReference-引用类型原子类
- AtomicMarkableReference-带有标记位的引用类型原子类
- AtomicStampedReference-带有版本号的引用类型原子类
AtomicStampedReference类在引用类型原子类中,彻底地解决了ABA问题,其它的CAS能力与另外两个类相近,所以最具代表性。因此,本节只针对AtomicStampedReference进行说明。
示例:基于AtomicReference实现一个简单的自旋锁
publicclassAtomicReferenceDemo2{ privatestaticintticket=10; publicstaticvoidmain(String[]args){ threadSafeDemo(); } privatestaticvoidthreadSafeDemo(){ SpinLocklock=newSpinLock(); ExecutorServiceexecutorService=Executors.newFixedThreadPool(3); for(inti=0;i<5;i++){ executorService.execute(newMyThread(lock)); } executorService.shutdown(); } /** *基于{@linkAtomicReference}实现的简单自旋锁 */ staticclassSpinLock{ privateAtomicReferenceatomicReference=newAtomicReference<>(); publicvoidlock(){ Threadcurrent=Thread.currentThread(); while(!atomicReference.compareAndSet(null,current)){} } publicvoidunlock(){ Threadcurrent=Thread.currentThread(); atomicReference.compareAndSet(current,null); } } /** *利用自旋锁{@linkSpinLock}并发处理数据 */ staticclassMyThreadimplementsRunnable{ privateSpinLocklock; publicMyThread(SpinLocklock){ this.lock=lock; } @Override publicvoidrun(){ while(ticket>0){ lock.lock(); if(ticket>0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+ticket+"张票"); ticket--; } lock.unlock(); } } } }
原子类的实现基于CAS机制,而CAS存在ABA问题(不了解ABA问题,可以参考:Java并发基础机制-CAS的问题)。正是为了解决ABA问题,才有了AtomicMarkableReference和AtomicStampedReference。
AtomicMarkableReference使用一个布尔值作为标记,修改时在true/false之间切换。这种策略不能根本上解决ABA问题,但是可以降低ABA发生的几率。常用于缓存或者状态描述这样的场景。
publicclassAtomicMarkableReferenceDemo{ privatefinalstaticStringINIT_TEXT="abc"; publicstaticvoidmain(String[]args)throwsInterruptedException{ finalAtomicMarkableReferenceamr=newAtomicMarkableReference<>(INIT_TEXT,false); ExecutorServiceexecutorService=Executors.newFixedThreadPool(3); for(inti=0;i<10;i++){ executorService.submit(newRunnable(){ @Override publicvoidrun(){ try{ Thread.sleep(Math.abs((int)(Math.random()*100))); }catch(InterruptedExceptione){ e.printStackTrace(); } Stringname=Thread.currentThread().getName(); if(amr.compareAndSet(INIT_TEXT,name,amr.isMarked(),!amr.isMarked())){ System.out.println(Thread.currentThread().getName()+"修改了对象!"); System.out.println("新的对象为:"+amr.getReference()); } } }); } executorService.shutdown(); executorService.awaitTermination(3,TimeUnit.SECONDS); } }
AtomicStampedReference使用一个整型值做为版本号,每次更新前先比较版本号,如果一致,才进行修改。通过这种策略,可以根本上解决ABA问题。
publicclassAtomicStampedReferenceDemo{ privatefinalstaticStringINIT_REF="pool-1-thread-3"; privatefinalstaticAtomicStampedReferenceasr=newAtomicStampedReference<>(INIT_REF,0); publicstaticvoidmain(String[]args)throwsInterruptedException{ System.out.println("初始对象为:"+asr.getReference()); ExecutorServiceexecutorService=Executors.newFixedThreadPool(3); for(inti=0;i<3;i++){ executorService.execute(newMyThread()); } executorService.shutdown(); executorService.awaitTermination(3,TimeUnit.SECONDS); } staticclassMyThreadimplementsRunnable{ @Override publicvoidrun(){ try{ Thread.sleep(Math.abs((int)(Math.random()*100))); }catch(InterruptedExceptione){ e.printStackTrace(); } finalintstamp=asr.getStamp(); if(asr.compareAndSet(INIT_REF,Thread.currentThread().getName(),stamp,stamp+1)){ System.out.println(Thread.currentThread().getName()+"修改了对象!"); System.out.println("新的对象为:"+asr.getReference()); } } } }
四、数组类型
Java提供了以下针对数组的原子类:
- AtomicIntegerArray-整形数组原子类
- AtomicLongArray-长整型数组原子类
- AtomicReferenceArray-引用类型数组原子类
已经有了针对基本类型和引用类型的原子类,为什么还要提供针对数组的原子类呢?
数组类型的原子类为数组元素提供了volatile类型的访问语义,这是普通数组所不具备的特性——volatile类型的数组仅在数组引用上具有volatile语义。
示例:AtomicIntegerArray使用示例(AtomicLongArray、AtomicReferenceArray使用方式也类似)
publicclassAtomicIntegerArrayDemo{ privatestaticAtomicIntegerArrayatomicIntegerArray=newAtomicIntegerArray(10); publicstaticvoidmain(finalString[]arguments)throwsInterruptedException{ System.out.println("InitValues:"); for(inti=0;i五、属性更新器类型
更新器类支持基于反射机制的更新字段值的原子操作。
- AtomicIntegerFieldUpdater-整型字段的原子更新器。
- AtomicLongFieldUpdater-长整型字段的原子更新器。
- AtomicReferenceFieldUpdater-原子更新引用类型里的字段。
这些类的使用有一定限制:
因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
- 字段必须是volatile类型的;
- 不能作用于静态变量(static);
- 不能作用于常量(final);
publicclassAtomicReferenceFieldUpdaterDemo{ staticUseruser=newUser("begin"); staticAtomicReferenceFieldUpdaterupdater= AtomicReferenceFieldUpdater.newUpdater(User.class,String.class,"name"); publicstaticvoidmain(String[]args){ ExecutorServiceexecutorService=Executors.newFixedThreadPool(3); for(inti=0;i<5;i++){ executorService.execute(newMyThread()); } executorService.shutdown(); } staticclassMyThreadimplementsRunnable{ @Override publicvoidrun(){ if(updater.compareAndSet(user,"begin","end")){ try{ TimeUnit.SECONDS.sleep(1); }catch(InterruptedExceptione){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"已修改name="+user.getName()); }else{ System.out.println(Thread.currentThread().getName()+"已被其他线程修改"); } } } staticclassUser{ volatileStringname; publicUser(Stringname){ this.name=name; } publicStringgetName(){ returnname; } publicUsersetName(Stringname){ this.name=name; returnthis; } } } 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。