1. 概述
- JVM中的CAS操作,Compare And Swap,它依靠处理器的
CMPXCHG
指令实现原子操作 - 执行
CMPXCHG
指令,需要三个操作数:内存地址 V、旧的预期值 A 和新值 B。 - 执行操作时,只有当内存 地址V中 的值等于 A,才将内存地址 V中 的值更新为 B。
1.1 什么是原子操作
- 原子操作的书面定义:不可中断的一个或一系列操作
- 这样的定义是晦涩难懂的,可以结合银行转账的例子来说明
- lucy向Bob转账100元,预期的操作步骤:lucy的账户减少100元,Bob的账户增加100元
- 银行系统不是很智能,lucy的账户减少100元后,由于某些问题系统服务崩溃
- 即使系统恢复了,既没有将Bob的账户增加100元,完成转账操作;也没有恢复lucy账户,告知转账失败
- 可以说,银行转账的过程应该是一个整体,其顺序不能被打乱,也不能被突然中止;要么都执行成功,要么都执行失败或者不执行
- 因此,原子操作可以这样理解:
- 计算机系统中一个流程,可能是一个步骤,也可能是多个步骤
- 流程的执行顺序不可以打乱,也不可以被切割而只执行其中的一部分
- 应该将整个流程视为不可以改变、分割的整体,就像一个原子一样
1.2 原子操作的重要性
- 需求
- 地铁一共有5个闸机口,设计一个程序统计总的进站人数
- 为了方便模拟进站计数,假设每个闸机口固定为1万人
- 有人会说,这样一规定直接可以口算啊。
- 有时候吧,人和机器就是有差别的,让机器实现计数功能,都不一定能准确呢 😂
半罐水的菜鸟的实现
-
这个简单,开5个线程,每个线程都让计数器的值加一1万次
-
使用volatile,保证上一个线程修改计数器的值后,下一个线程立马可见
public class Counter { private static volatile int i; private static final int THREAD_COUNT = 5; private static final CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT); public static void count() { i++; } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[THREAD_COUNT]; // 创建计数线程并开始计数 for (int i = 0; i < THREAD_COUNT; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < 10000; j++) { count(); } countDownLatch.countDown(); }); threads[i].start(); } // 完成计数后,打印值 countDownLatch.await(); System.out.println("今日总进站人数: " + i); } }
-
一执行代码,傻眼了:怎么最后结果不是5万?而且差的也太远了吧 😂
-
原因: volatile只能多线程下的可见性,不能保证非原子操作的线程安全
-
i++
操作不是一个原子操作,导致putstatic
放回去的可能是较小值 -
情况一: 重复计数问题,导致
putstatic
放回去的是较小值时间 线程1 线程2 结果 t1 getstatic getstatic 线程1和2,都获取到 i
的最新值 20t2 inconst_1
iaddinconst_1
iadd线程1和2,各自的工作内存中 i
的值均加1t3 putstatic 线程2将自己工作内存中的 i = 21
写回主内存t4 putstatic 线程1自己工作内存中的 i = 21
写回主内存 -
还可能有更极端的情况:
- 线程1执行
getstatic
获取到i
的最新值20后,由于CPU时间耗尽被挂起 - 之后其他线程正常工作,已经通过
putstatic
将i
更新为了24 - 线程1再次获取大CPU时间片,仍然基于旧的20进行加一操作;执行
putstatic
将21写回到内存,一下子就将之前的计数工作打回原形
- 线程1执行
加锁
- 通过
synchronized
关键给count() 方法加锁,保证同一时刻只有一个线程将计数器加一 - 计数正确,但是同一时刻只能有一个线程访问count() 方法,计数效率十分低下
- 就像过闸机时,只有一个工作人员在那里数数,实际是串行计数
通过原子类实现线程安全的计数器
-
既然
i++
操作不是原子操作,可以考虑使用Java的原子类,进行具有原子性的加一操作- 循环的CAS操作,每次加一前都先获取当前值
- 在当前线程执行CAS加一操作前,若没有其他线程更新值,则CAS操作成功;否则,进行下一次CAS加一操作的尝试直到成功
public class Counter { private static final int THREAD_COUNT = 5; private static final CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT); private AtomicInteger i = new AtomicInteger(); public void count() { // 通过循环的CAS实现原子加一 for (; ; ) { int current = i.get(); if (i.compareAndSet(current, ++current)) { break; } } } public int getValue(){ return i.get(); } public static void main(String[] args) throws InterruptedException { Thread[] threads = new Thread[THREAD_COUNT]; Counter counter = new Counter(); // 创建计数线程并开始计数 for (int i = 0; i < THREAD_COUNT; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < 10000; j++) { counter.count(); } countDownLatch.countDown(); }); threads[i].start(); } // 完成计数后,打印值 countDownLatch.await(); System.out.println("今日总进站人数: " + counter.getValue()); } }
-
多线程下,基于原子类、通过循环的CAS加一操作成功计数:
2. CAS实现原子操作的三个问题
2.1 ABA问题
- 基于CAS操作,考虑银行系统转账场景
- 小明账户余额100元,通过手机银行向小王转账50元
- 由于系统内部错误,出现两个线程同时进行转账操作
- 线程1获取账户余额100作为期望值,比较后发现符合,于是将账户余额更新为50元(转账成功)
- 线程2同一时间,也获取账户余额100作为期望值,然后block了
- 之后,小王还款50元,账户余额变成100元
- 线程2恢复,比较后发现符合预期,于是再次执行转账操作、小明的户余额又变成了50元
- 这就是所谓的ABA问题:
- 一个数的值原来是A —> 更新成B —> 更新回A
- 这时使用CAS进行检查是发现它没有发生变化,于是成功更新
- 实际上,这个数已经变化多次,只是最后变回了A而已
- ABA问题,可以通过添加版本号解决:A —> B —> A添加版本号后,1A —> 2B —> 3A
- 这时,发现不是期望的1A,而是3A,因此不会进行CAS更新
JDK提供的解决方案 —— AtomicStampedReference
原子类
- JDK的
java.util.concurrent.atomic
包,简称Atomic包,里面提供了12中支持原子操作的实用类 - 其中,
AtomicStampedReference
类可以原子更新带有版本号的引用类型 - 类中的compareAndSet() 方法,要求预期引用等于当前引用、预期版本号等于当前版本号,然后以原子的方式更新引用和版本号
- 完成版本号和引用校验后,调用casPair() 方法进行原子更新
- casPair() 方法最终将调用Unsafe类的native方法compareAndSwapObject() 进行原子更新
public boolean compareAndSet(V expectedReference, // 预期引用 V newReference, // 更新后的引用 int expectedStamp, // 预期版本号 int newStamp) { // 更新后的版本号 Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); } private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); }
- 自己的猜测CMPXCHG指令的实现:
- 更新时会锁定内存、获取内存中的值
- 如果发现内存中的值与预期值不一致,则不进行更新并返回false
- 如果内存中的值与预期值一致,则进行更新并会返回true
- 也就是说,casPair() 方法只是尝试进行一次原子更新,并不保证此次更新一定能成功
- 这也是为啥实际调用compareAndSet() 时,需要以死循环的方式(自旋CAS)进行调用
2.2 循环时间长、开销大
- 自旋CAS和自旋锁一样,如果长时间不成功,会给CPU带来较大的开销
- 针对这个问题,貌似没有给出解决方案,就是说:如果JVM能支持操作系统的pause指令,那么效率会有一定的提升
① 可以延迟流水线执行指令,是CPU不会消耗过多的执行资源
② 可以避免在退出循环的时候因为内存顺序冲突,导致CPU流水线被清空,从而提高CPU的执行效率
2.3 只能保证一个共享变量的原子操作
-
之前的地铁乘车流量计数器,只能计算进站人数,如果要求同时计算男女生人数
-
照葫芦画瓢,在定义两个AtomicInteger类型的变量,统计男生或女生人数
public void count() { // CAS计算进站总人数 for (; ; ) { int current = total.get(); if (total.compareAndSet(current, ++current)) { break; } } // CAS计算进站男生人数 if (men) { for (; ; ) { int current = men.get(); if (men.compareAndSet(current, ++current)) { break; } } } ... }
-
上面的代码存在问题,线程在完成进站总人数的更新后,如果线程中断未完成男女生的计数,会导致最终统计的数值存在问题
-
因此,循环CAS操作只能保证对一个共享变量的原子操作,要想对多个变量执行原子操作:
- 方法一:加锁,对多个共享变量的操作放到同一个临界区,保证操作的原子性
- 方法二:取巧,将多个变量合为一个支持原子操作的变量,例如,
i
、j
组合成k = i * j
- 方法三:使用AtomicReference类保证引用类型的原子性,其中多个共享变量作为引用类型的成员变量
3. Atomic包中的原子操作类
- JDK的Atomic包,提供了很多实用的原子操作类
- 包括:原子更新基本数据类型、原子更新数组、原子更新引用类型、原子更新字段
3.1 Unsafe类
- 这些原子操作类,几乎都是基于Unsafe类实现CAS操作
- Unsafe类中实现CAS操作的方法都是native方法:支持对象、int和long三种类型的原子操作
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5); public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
3.2 原子更新基本数据类型
- Java中的基本数据类型有:boolean、byte、short、char、int、long、float、double
- Atomic包提供了针对boolean、int和long三种基本数据类型的原子操作类:
AtomicBoolean
、AtomicInteger
、AtomicLong
- 下面将基于AtomicInteger进行讲解
其成员变量如下:
- unsafe:Unsafe类的实例,是实现原子操作的基础
- value:就是AtomicInteger对应的数值,使用
volatile
修饰,表示多线程访问时的可见性(后续会详细说) - valueOffset:value字段的内存地址偏移量,可以根据偏移量计算绝对地址,后续更新数值需要使用该内存地址
private static final Unsafe unsafe = Unsafe.getUnsafe(); 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;
compareAndSet() 方法:
- 尝试进行一次CAS操作,将value更新从预期值(expect)更新为指定值(update);成功返回true,否则返回false
- 实际调用Unsafe类的
compareAndSwapInt()
方法,尝试进行一次CAS操作public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
- 注意: 这里只是尝试进行一次CAS操作,若想要保证一定会成功,则需要进行循环的CAS操作,
- 就像之前的count代码一样:
-
不停获取计数器的最新值作为预期值,然后尝试通过CAS操作将其加1
-
最新值,由AtomicInteger的value字段前的
volatile
关键字保证public void count() { for (; ; ) { int current = i.get(); if (i.compareAndSet(current, ++current)) { break; } } }
-
一些实用方法
-
compareAndSet() 方法,使用时还需要自己通过循环保证CAS操作的成功性,比较麻烦
-
AtomicInteger中也提供了很多实用的方法,这些方法自己保证CAS操作的成功性
-
这些实用方法分为两类:返回旧值、返回新值
-
返回旧值
public final int getAndIncrement() // 原子自增 public final int getAndDecrement() // 原子自减 public final int getAndAdd(int delta) // 原子增加指定值 public final int getAndSet(int newValue) // 原子更新为指定值
-
返回新值
public final int incrementAndGet() public final int decrementAndGet() public final int addAndGet(int delta)
-
这些实用方法,最终都调用了Unsafe类的
getAndAddInt()
方法public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); } public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
-
getAndAddInt() 方法内部,通过循环CAS操作实现原子更新,这也是这些方法为什么更实用的原因
-
var1 是当前对象,var2 是value在对象中的offset,通过 var1 和 var2 可以计算出value的真实内存
-
var4 是变化量, var5 是获取的value最新值作为预期值,
var5 + var4
就是新值 -
然后调用
compareAndSwapInt()
尝试将value从 var5 原子更新为var5 + var4
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); // 每次都获取内存中的最新值 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
-
如何实现其他基本数据类型的原子更新?
- Atomic包中,只提供了对boolean、int、long三种类型对应的原子操作类
- 那其他的基本数据类型如何实现原子操作呢?
- AtomicInteger实现原子操作最终使用Unsafe类的
compareAndSwapInt()
方法 - 回到Unsafe类,之前了解过它有三个实现原子操作的方法,分别针对对象、int和long
- 因此,可以猜测AtomicLong最终使用Unsafe类的
compareAndSwapLong()
方法实现原子操作 - 在Unsafe类中,没有针对boolean类型的类似方法,AtomicBoolean的原子操作实际依靠
compareAndSwapInt()
方法实现 - 将Boolean类型转为int的1、0,表示true、false,从而可以使用
compareAndSwapLong()
方法实现CAS操作public final boolean compareAndSet(boolean expect, boolean update) { int e = expect ? 1 : 0; int u = update ? 1 : 0; return unsafe.compareAndSwapInt(this, valueOffset, e, u); }
- 因此,其他未提供对应原子类的基本数据类型,也可以考虑使用类似的替换方法
3.3 原子更新数组
- Atomic包还提供以下3个类实现数组中元素的原子更新
- AtomicIntegerArray:原子更新整型数组中的元素
- AtomicLongArray:原子更新长整型数组中的元素
- AtomicReferenceArray :原子更新引用类型数组中的元素
- 下面将以 AtomicLongArray为例,讲解相关的实现
成员变量
unsafe
:还是依靠Unsafe类实现数组元素的原子更新base
:数组元素的基准offsetshift
:位移运算中的位移数,实际为3
,其作用后续分析array
: 实际存储long元素的数组private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final int base = unsafe.arrayBaseOffset(long[].class); private static final int shift; private final long[] array; static { int scale = unsafe.arrayIndexScale(long[].class); if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); shift = 31 - Integer.numberOfLeadingZeros(scale); }
- array为final类型,说明说明AtomicLongArray定义的long数组是不可变的
- 这里的不可变,不是指数组中元素不可变,而是指数组这个外壳不可变
- 也就是说,一旦初始化后,不会再重新分配新的内存
- 这样的话,数组的base也不会变
compareAndSet()方法
- AtomicLongArray的compareAndSet() 方法,需要提供元素的index、期望值和新值
- 实现CAS操作的调用链为
compareAndSet()
→ \rightarrow →compareAndSetRaw()
→ \rightarrow →unsafe.compareAndSwapLong()
public final boolean compareAndSet(int i, long expect, long update) { return compareAndSetRaw(checkedByteOffset(i), expect, update); } private boolean compareAndSetRaw(long offset, long expect, long update) { return unsafe.compareAndSwapLong(array, offset, expect, update); }
- 其中,compareAndSetRaw() 方法的offset:是通过下标
i
计算出的元素相对起始位置的offset- byteOffset() 方法实现了offset的计算:
(1)i << shift
,即i << 3
,也就是乘以 2 3 = 8 2^3 = 8 23=8
(2)一个long类型的字节数为8,则i << 3
计算出来的就是索引为i
的元素相对起始元素的offset
(3)相对起始元素的offset加上基准offset,就可以确定索引为i
的元素的内存地址private long checkedByteOffset(int i) { if (i < 0 || i >= array.length) throw new IndexOutOfBoundsException("index " + i); return byteOffset(i); } private static long byteOffset(int i) { return ((long) i << shift) + base; // 计算offset的关键 }
- byteOffset() 方法实现了offset的计算:
注意:
- 同AtomicInteger的compareAndSet()方法一样,AtomicLongArray的compareAndSet() 方法也只是尝试进行一次数组元素的更新,可能成功,也可能失败
- 使用时,可以通过循环保证CAS操作的成功
实用方法
-
同样的,AtomicLongArray也提供了一些保证成功原子更新数组元素的实用方法,而无需使用时自动添加循环CAS的有关代码
-
获取新值的更新方法,其中
i
表述元素的索引public long addAndGet(int i, long delta) public final long decrementAndGet(int i) public final long incrementAndGet(int i) public final int getAndSet(int i, int newValue)
-
获取旧值的更新方法
public final long getAndAdd(int i, long delta) public final long getAndDecrement(int i) public final long getAndIncrement(int i)
3.4 原子更新引用类型
- 上面提到过,解决CAS操作的ABA问题、只能原子更新一个共享变量的问题,都提到了使用
AtomicXxxReference
原子类实现 - 其实质都是将多个变量看做一个整体,也就是引用类型,从而对其进行更新
- Atomic包提供了以下3个原子更新引用类型的类:
-
AtomicReference:原子更新引用类型,也就是以原子的方式更新一个对象
public final boolean compareAndSet(V expect, V update) { return unsafe.compareAndSwapObject(this, valueOffset, expect, update); }
-
AtomicStampedReference:原子更新带有版本号的引用类型,只有当前版本号、引用类型与预期版本号、引用类型一致时,才会更新成的版本号与预期类型
public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
-
AtomicMarkableReference:原子更新带有标记的引用类型,只有当前标记、引用类型与预期标记、引用类型一致时,才会更新成的标记与预期类型
public boolean compareAndSet(V expectedReference, V newReference, boolean expectedMark, boolean newMark) { Pair<V> current = pair; return expectedReference == current.reference && expectedMark == current.mark && ((newReference == current.reference && newMark == current.mark) || casPair(current, Pair.of(newReference, newMark))); }
-
- 原子更新引用类型,实际调用的都是unsafe类的compareAndSwapObject() 方法
- 下面以AtomicStampedReference来看看原子更新应用类型的实现
成员变量
- 三个成员变量
UNSAFE
:Unsafe类的实例对象,是实现原子更新的基础pair
:将版本号和引用类型合成一个新的类,因此可以同时保证版本号和引用类型的原子更新pairOffset
:计算pair字段在类中的偏移量,从而可以计算出pair字段真实的内存地址private volatile Pair<V> pair; private static final sun.misc.Unsafe UNSAFE = sun.misc.Unsafe.getUnsafe(); private static final long pairOffset = objectFieldOffset(UNSAFE, "pair", AtomicStampedReference.class); private static class Pair<T> { final T reference; 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()方法
-
代码如下,实质调用casPair方法完成pair(版本号和引用类型)的原子更新
private boolean casPair(Pair<V> cmp, Pair<V> val) { return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val); }
-
除了同时原子更新版本号和引用类型,还可以单独原子更新版本号
public boolean attemptStamp(V expectedReference, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && (newStamp == current.stamp || casPair(current, Pair.of(expectedReference, newStamp))); }
关于实用方法
- 原子更新应用类型的几个类,没有像之前的类一样,提供一些实用方法
- 想想也是有道理的,就拿AtomicStampedReference类来说,就算每次都将版本号递增,但是引用类型中的字段呢?
- 这些字段各种类型都有,值的更新是无法预料的
- 而之前的int或long的原子操作一样,很单纯地就只有加减运算更新value,因此能提供一些实用方法
3.5 原子更新字段
-
Atomic包都提供了对引用类型的整体更新,同样,也提供了原子更新对象中某个字段的类
-
AtomicIntegerFieldUpdater:原子更新对象中int类型的字段
-
AtomicLongFieldUpdater:原子更新对象中long类型的字段
-
AtomicReferenceFieldUpdater:原子更新引用类型的字段,这个是顶配。
public abstract class AtomicIntegerFieldUpdater<T> public abstract class AtomicLongFieldUpdater<T> public abstract class AtomicReferenceFieldUpdater<T,V>
-
-
所有的基本数据类型都有对应的包装类,因此它可以更新基本数据类型和引用类型,涵盖几乎所有的更新需求
-
下面将以AtomicIntegerFieldUpdater为例,看看其如何实现字段的原子更新
如何创建一个更新器?
-
从类的定义可以看出,原子更新字段的相关类实际都是抽象类,因此无法通过
new AtomicIntegerFieldUpdater()
创建一个更新器 -
原子更新字段的这些抽象类,都在自己内部定义了一个静态实现类。
-
AtomicIntegerFieldUpdater的实现类定义如下
private static final class AtomicIntegerFieldUpdaterImpl<T> extends AtomicIntegerFieldUpdater<T>
-
但是,静态实现类是private的,外部类也无法调用该实现类的构造方法创建一个更新器
-
因此,在抽象类中提供了一个工具方法
newUpdater()
以创建对应的更新器tclass
:字段所在的类fieldName
:字段名public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass, String fieldName) { return new AtomicIntegerFieldUpdaterImpl<U> (tclass, fieldName, Reflection.getCallerClass()); }
-
留下一个疑问: 为啥不是直接传入一个引用类型(实例对象),而是传入所在类的Class对象?
一点题外话
- 关于字段名,最开始自己的设想是,知道Class对象、字段名、字段类型(int),那很容易通过反射更新该字段
- 后来看了具体的实现,发现自己忽略了CAS操作的实质:预期值与当前内存地址中的值做比较,一致则将内存地址中的值更新为新值
- 整个操作时针对内存的直接读写操作,根本无需使用反射
实现类的成员变量和构造函数
- 实现类的成员变量和构造函数如下
- 不出意外,成员变量中存在Unsafe类实例
U
、字段在对象中的offset
、对应类的Class对象tclass
- 关于
cclass
,按照注释和相关资料的介绍:要么为调用者类的Class对象,要么为tclass
(自己目前不是很理解) - 构造函数为了初始化这些成员变量,做了很多事情:
- 通过反射获取字段的Field对象以及修饰符,要求是可访问的、
volatile
的int类型字段 - 获取所在类和调用者的类加载器
- 还有访问权限、int类型、volatile修饰符的校验,字段在对象中的offset的计算、
cclass
的更新等
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe(); private final long offset; private final Class<?> cclass; private final Class<T> tclass; AtomicIntegerFieldUpdaterImpl(final Class<T> tclass, final String fieldName, final Class<?> caller) { // 通过反射获取字段的Field对象以及修饰符 final Field field; final int modifiers; try { field = AccessController.doPrivileged( new PrivilegedExceptionAction<Field>() { public Field run() throws NoSuchFieldException { return tclass.getDeclaredField(fieldName); } }); modifiers = field.getModifiers(); sun.reflect.misc.ReflectUtil.ensureMemberAccess( caller, tclass, null, modifiers); // 获取类加载器 ClassLoader cl = tclass.getClassLoader(); ClassLoader ccl = caller.getClassLoader(); if ((ccl != null) && (ccl != cl) && ((cl == null) || !isAncestor(cl, ccl))) { sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass); } } catch (PrivilegedActionException pae) { throw new RuntimeException(pae.getException()); } catch (Exception ex) { throw new RuntimeException(ex); } // 类型必须为int if (field.getType() != int.class) throw new IllegalArgumentException("Must be integer type"); // 修饰符中应该包含volatile if (!Modifier.isVolatile(modifiers)) throw new IllegalArgumentException("Must be volatile type"); // 这里不是很理解,暂时理解成cclass的更新吧 this.cclass = (Modifier.isProtected(modifiers) && tclass.isAssignableFrom(caller) && !isSamePackage(tclass, caller)) ? caller : tclass; this.tclass = tclass; this.offset = U.objectFieldOffset(field); // 计算字段在对象中的offset }
- 通过反射获取字段的Field对象以及修饰符,要求是可访问的、
- 不出意外,成员变量中存在Unsafe类实例
compareAndSet()方法
- 在AtomicIntegerFieldUpdater抽象类中, compareAndSet() 方法是一个抽象方法
- AtomicIntegerFieldUpdaterImpl实现类,实现了该方法
- 传入实例对象、预期值、新值,然后调用Unsafe类
compareAndSwapInt()
方法完成该字段的原子更新public final boolean compareAndSet(T obj, int expect, int update) { accessCheck(obj); return U.compareAndSwapInt(obj, offset, expect, update); }
- 这里传入了一个实例对象,而不是在创建更新器时就指定实例对象
- 这样的话,一个更新器,可以更新多个实例对象中的指定字段,而不是为每个实例对象单独创建更新器(设计巧妙而且灵活)
- 这也回答了上面的疑问:为啥不是直接传入一个引用类型(实例对象),而是传入所在类的Class对象?
实用方法
-
对int字段的更新和直接更新int类型的变量,实际是一样的
-
AtomicIntegerFieldUpdater也提供了很多实用方法,使用时无需添加额外的循环CAS代码
-
与其他操作int类型的原子类一样,这些实用方法实际调用Unsafe类
getAndAddInt()
方法 -
返回旧值
public final int getAndAdd(T obj, int delta) public final int getAndIncrement(T obj) public final int getAndDecrement(T obj) public final int getAndSet(T obj, int newValue)
-
返回新值
public final int addAndGet(T obj, int delta) public final int incrementAndGet(T obj) public final int decrementAndGet(T obj)
如何实现字段的原子更新
-
通过对AtomicIntegerFieldUpdater类的学习,发现原子更新字段类与其他原子操作类不一样
-
原子更新字段的相关类都是抽象类,需要通过工具方法newUpdater() 创建对应的更新器
-
因此,基于AtomicIntegerFieldUpdater类,原子更新int字段的diamante示例如下
public class FieldUpdater { private static class Student{ private String name; public volatile int age; public Student(String name, int age) { this.name = name; this.age = age; } } public static void main(String[] args) { AtomicIntegerFieldUpdater<Student> updater = AtomicIntegerFieldUpdater.newUpdater(Student.class, "age"); Student student = new Student("lucy", 21); // 新的一年,年龄增加一岁 System.out.println(student.name + "今年" + updater.incrementAndGet(student) + "岁"); } }
4. 总结
好多资料都说有13个原子操作类,但是自己只发现了12个,欢迎大家与我交流 ~
-
原子操作:执行一个操作,需要一步或多步,要求这些步骤不可分割且必须顺序执行
-
结合统计地铁进站人数的实例,理解原子操作的重要性
- volatile可以禁止指令重排序、保证多线程下变量的可见性,但不能保证非原子操作的线程安全
i++
自增操作不是原子操作- synchronized同步能保证线程安全,但是效率大打折扣,等同于只有一个计数器的情况
- 通过原子类AtomicInteger实现线程安全的计数器(如何实现循环CAS)
-
CAS实现原子操作的三大问题及解决方法:
- ABA问题,使用带码版本号的原子类AtomicStampReference解决
- 循环时间过长的问题:没有说解决方法,只说了JVM支持操作系统pause指令的好处
- 只能原子更新一个共享变量的问题:多个共享变量放到一个类中,通过AtomicReference类对整个引用类型的原子更新,从而实现对多个共享变量的原子更新
-
Java的Atomic包:
java.util.concurrent.atomic
- Unsafe类是基础,提供了原子更新int、long和对象的native方法
- 原子更新基本类型:boolean、int和long
- 原子更新数组:int、long和引用类型
- 原子更新引用类型:引用类型、带版本号的引用类型、带标记的引用类型
- 原子更新字段:int、long和引用类型
-
一些注意事项:
- 每个原子类都提供了compareAndSet() 方法,尝试进行一次CAS操作,并不一定能成功
- 原子类中的实用方法,实际调用的是Unsafe中已经实现了循环CAS的相关方法,如
getAndAddInt()
- 实用方法的分类:返回旧值(
get...
)、返回新值(...AndGet
)
-
除了循环CAS可以实现原子,锁机制也可以实现原子操作
- 除了偏向锁,JVM获取锁都使用了循环CAS的方式(自旋锁) —— 具体待验证
参考链接
- AtomicIntegerFieldUpdater的讲解:J.U.C 原子类系列之AtomicIntegerFieldUpdater、AtomicLongFieldUpdater和AtomicReferenceFieldUpdater
- 说12个原子操作类、讲解也挺不错的博客:java并发编程-12个原子类
- Java并发编程之原子类
- JDK 1.8新增原子类:Java中的13个原子操作类
- 有空可以再看看:面试必问的CAS,你懂了吗?