Atomic:原子类介绍

Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。所以,所谓原子类说简单点就是具有原子/原子操作特征的类。在java.util.concurrent.atomic下。

根据操作的数据类型,可以将 JUC 包中的原子类分为 4 类:

基本类型

使用原子的方式更新基本类型

  AtomicInteger:整型原子类

  AtomicLong:长整型原子类

  AtomicBoolean :布尔型原子类

复制代码
import java.util.concurrent.atomic.AtomicInteger;
public
final int get() //获取当前的值 public final int getAndSet(int newValue)//获取当前的值,并设置新的值 public final int getAndIncrement()//获取当前的值,并自增 public final int getAndDecrement() //获取当前的值,并自减 public final int getAndAdd(int delta) //获取当前的值,并加上预期的值 boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update) public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
private AtomicInteger count = new AtomicInteger();
复制代码
基本数据类型原子类的优势:使用AtomicInteger之后,不需要加锁,也可以实现线程安全。比如自增,多线程环境下需要给count++;所在方法加synchronized关键字,使用原子类可以直接使用,count.incrementAndGet();
复制代码
  //第一步获取到unsafe就能执行CAS操作,获取到unsafe变量  
  private
static final Unsafe unsafe = Unsafe.getUnsafe();

  //第二步获取到共享变量的偏移量
  //根据UNSAFE对象和字段field获取到字段在对象里的偏移量, 偏移量可以用sun.misc.Unsafe类里提供一个native方法objectFieldOffset(Field field)方法来获取。
private static final long valueOffset; static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value; AtomicInteger 类主要利用 CAS (compare and swap) + volatilenative 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。 CAS 的原理是拿期望的值(旧值(“期望不变”)E)和原本的一个值(内存中的值V)作比较,如果相同则更新成新的值(N)。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址。
另外 value 是一个
volatile 变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
复制代码

 

数组类型

使用原子的方式更新数组里的某个元素

  AtomicIntegerArray:整型数组原子类

  AtomicLongArray:长整型数组原子类

  AtomicReferenceArray :引用类型数组原子类

int[] nums = { 1, 2, 3, 4, 5, 6 };
AtomicIntegerArray i = new AtomicIntegerArray(nums);

temvalue = i.getAndSet(0, 2);

 

引用类型

  AtomicReference:引用类型原子类

  AtomicMarkableReference:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来(AtomicMarkableReference是将一个boolean值作是否有更改的标记,本质就是它的版本号只有两

个,true和false,修改的时候在这两个版本号之间来回切换,每次更新标志位的话,在第三次的时候,又会变得跟第一次一样,并不能解决ABA的问题,只是会降低ABA问题发生的几率而已)。

  AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题(具体的原理: 每次CAS前,先获取当AtomicStampedReference对象里的Pair里的stamp和reference, 该stamp为版本号,reference为期望的对象值,如果获取到的版本号为期望的版本号并且获取到的reference值为期望值,就是说当前线程获取到的版本号在CAS前没有被其他线程修改且reference也没有修改,那么当前线程才能进行CAS操作,如果版本号不是期望值,那么就不执行更新操作)。

   基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。

AtomicReference <Person> ar = new AtomicReference <Person> ();
Person person = new Person("SnailClimb", 22);
ar.set(person);
Person updatePerson = new Person("Daisy", 20);
ar.compareAndSet(person, updatePerson);

首先创建一个 Person 对象,然后把 Person 对象设置进 AtomicReference 对象中,然后调用 compareAndSet 方法,该方法就是通过 CAS 操作设置 ar。
如果 ar 的值为 person 的话,则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。

 

复制代码
//AtomicStampedReference初始化方法 
public AtomicStampedReference(V initialRef, int initialStamp) {
    pair = Pair.of(initialRef, initialStamp);
}    

//Pair是一个内部类
private static class Pair<T> {
     //reference即实际存储的变量
     final T reference;
     //stamp是版本
     final int stamp;
     private Pair(T reference, int stamp) {
         this.reference = reference;
         this.stamp = stamp;
     }
     static <T> Pair<T> of(T reference, int stamp) {
         return new Pair<T>(reference, stamp);
     }
}
//CompareAndSet
//
原子性的赋值,如果当前reference和stamp对应地等于期望的数据。 public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp,int newStamp) { //当前Pair对象 Pair<V> current = pair;
     //期望的数据和当前数据相等、期望的stamp和当前stamp相等、赋值成功或casPair返回true,这三个条件均为true则返回true
     
return expectedReference == current.reference &&
         expectedStamp == current.stamp &&
        ((newReference == current.reference && newStamp == current.stamp)//赋值(引用类型和原始类型的赋值为原子操作)
        ||casPair(current, Pair.of(newReference, newStamp)) );
} //casPair方法,实际就是UNSAFE.compareAndSwapObject方法 private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
  } 
复制代码

ABA例子:

1、小明银行卡账户余额1万元, 去取款机取钱5千元, 正常应该发起一次请求, 因为网络或机器故障发起了两次请求
2、线程一「由取款机发起」: 获取当前银行卡余额值1万元, 期望值5千元
3、线程二「由取款机发起」: 获取当前银行卡余额值1万元, 期望值5千元
4、线程一成功了, 银行卡内余额剩余五千, 线程二未分配时间片, 获取到余额后被阻塞
5、此时线程三「支付宝转入银行卡」: 获取当前银行卡余额5千元, 期望值1万元, 修改成功
6、线程二获得时间片, 发现卡内余额是1万, 成功将卡内余额减少至5千
7、卡内余额原本应是1万-5千+5千=1万, 最终因为 ABA 问题导致 1万-5千+5千-5千=5千

https://developer.aliyun.com/article/1037332

 

对象的属性修改类型

  • AtomicIntegerFieldUpdater:原子更新整型字段的更新器
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器
  • AtomicReferenceFieldUpdater:原子更新引用类型里的字段
posted @   壹索007  阅读(111)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示