Java多线程_Atomic 原子类
一、介绍一下Atomic 原子类
Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是构成一般物质的最小单位,在化学反应中是不可分割的。在我们这里 Atomic是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
所以,所谓原子类说简单点就是具有原子/原子操作特征的类。
并发包 java.util.concurrent 的原子类都存放在 java.util.concurrent.atomic 下,如下图所示。

二、JUC 包中的原子类是哪4类
使用原子的方式更新基本类型
AtomicInteger:整形原子类
AtomicLong:长整型原子类
AtomicBoolean :布尔型原子类
使用原子的方式更新数组里的某个元素
AtomicIntegerArray:整形数组原子类
AtomicLongArray:长整形数组原子类
AtomicReferenceArray :引用类型数组原子类
引用类型:
AtomicReference:引用类型原子类
AtomicStampedRerence:原子更新引用类型里的字段原子类
AtomicMarkableReference :原子更新带有标记位的引用类型
对象的属性修改类型:
AtomicIntegerFieldUpdater:原子更新整形字段的更新器
AtomicLongFieldUpdater:原子更新长整形字段的更新器
AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原 子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题
三、讲讲AtomicInteger的使用
AtomicInteger类常用方法:
1 public final int get() //获取当前的值 2 public final int getAndSet(int newValue)//获取当前的值,并设置新的值 3 public final int getAndIncrement()//获取当前的值,并自增 4 public final int getAndDecrement() //获取当前的值,并自减 5 public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 6 boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输 7 入值(update) 8 public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程 9 在之后的一小段时间内还是可以读到旧的值。
AtomicInteger 类的使用示例:
使用 AtomicInteger 之后,不用对 increment() 方法加锁也可以保证线程安全。
1 class AtomicIntegerTest { 2 private AtomicInteger count = new AtomicInteger(); 3 //使用AtomicInteger之后,不需要对该方法加锁,也可以实现线程安全。 4 public void increment() { 5 count.incrementAndGet(); 6 } 7 public int getCount() { 8 return count.get(); 9 } 10 }
四、简单介绍一下 AtomicInteger 类的原理
AtomicInteger 类的部分源码:
1 // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) 2 private static final Unsafe unsafe = Unsafe.getUnsafe(); 3 private static final long valueOffset; 4 static { 5 try { 6 valueOffset = unsafe.objectFieldOffset 7 (AtomicInteger.class.getDeclaredField("value")); 8 } catch (Exception ex) { throw new Error(ex); } 9 } 10 private volatile int value;
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法 是一个本地方法,这个方法是用来拿到“原来的值”的内存地址,返回值是 valueOffset。另外 value 是一个volatile变 量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
举个栗子
- 在一个内存地址为V内存中,储存着变量值10(即此时A=10)
- 此时,来了一个线程A,想对该变量进行增加1操作(即此时对线程A来说:A=10,B=11)
- 但是,当线程A操作之前,被线程B插入率先将该变量进行修改成11(即此时A=11)
- 接着,等线程A想来修改的时候,通过内存地址V获取当前最新的值与自身线程的预期值进行比对(此时线程A的A=10),发现不一样,导致更新失败
- 线程A重新获取内存地址V的当前值,并重新计算想要修改的新值,即自旋操作。(此时对线程A来说,A=11,B=12)
- 这一次没有其他线程的干扰,线程A通过获取内存地址V的实际值,成功比较后,并且将内存地址V的值修改成12
缺点
- cpu开销大,在高并发下,许多线程,更新一变量,多次更新不成功,循环反复,给cpu带来大量压力
- 只是一个变量的原子性操作,不能保证代码块的原子性
- ABA问题(不过一般极少出现)
举个栗子(ABA问题)
- 小埋去存银行卡有200块钱,去提款机取钱100,恰巧机器有点问题,在进行取款提交操作后台开启了两个线程A和B(200->100)
- 线程A成功执行,而且线程B阻塞了,此时小埋银行卡的余额为100
- 此时,小埋的哥哥给她卡了打了100块,此时小埋银行卡的余额为200
- 然后,线程B此时就又开始执行了,发现卡里的余额200和自身线程余额期待值一样,进行修改(200->100)
- 最后,小埋就只能去找柜台寻求帮助了,因为正常情况下,她现在的卡里应该还有200块
如何解决aba问题:
对内存中的值加个版本号,在比较的时候除了比较值还的比较版本号。

浙公网安备 33010602011771号