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 可以保证任何时刻任何线程总能拿到该变量的最新值。

举个栗子

  1.  在一个内存地址为V内存中,储存着变量值10(即此时A=10)
  2. 此时,来了一个线程A,想对该变量进行增加1操作(即此时对线程A来说:A=10,B=11)
  3. 但是,当线程A操作之前,被线程B插入率先将该变量进行修改成11(即此时A=11)
  4. 接着,等线程A想来修改的时候,通过内存地址V获取当前最新的值与自身线程的预期值进行比对(此时线程A的A=10),发现不一样,导致更新失败
  5. 线程A重新获取内存地址V的当前值,并重新计算想要修改的新值,即自旋操作。(此时对线程A来说,A=11,B=12)
  6. 这一次没有其他线程的干扰,线程A通过获取内存地址V的实际值,成功比较后,并且将内存地址V的值修改成12

缺点

  • cpu开销大,在高并发下,许多线程,更新一变量,多次更新不成功,循环反复,给cpu带来大量压力
  • 只是一个变量的原子性操作,不能保证代码块的原子性
  • ABA问题(不过一般极少出现)

举个栗子(ABA问题)

  1. 小埋去存银行卡有200块钱,去提款机取钱100,恰巧机器有点问题,在进行取款提交操作后台开启了两个线程A和B(200->100)
  2. 线程A成功执行,而且线程B阻塞了,此时小埋银行卡的余额为100
  3. 此时,小埋的哥哥给她卡了打了100块,此时小埋银行卡的余额为200
  4. 然后,线程B此时就又开始执行了,发现卡里的余额200和自身线程余额期待值一样,进行修改(200->100)
  5. 最后,小埋就只能去找柜台寻求帮助了,因为正常情况下,她现在的卡里应该还有200块

如何解决aba问题:
对内存中的值加个版本号,在比较的时候除了比较值还的比较版本号。

posted @ 2019-08-06 21:55  chyblogs  阅读(458)  评论(0)    收藏  举报