📦 本文以及示例源碼已歸檔在 javacore
一、原子變量類簡介
為何需要原子變量類
保證線程安全是 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
用法
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 設置之后可能導致其他線程在之后的一小段時間內還是可以讀到舊的值。
AtomicInteger
使用示例:
public class AtomicIntegerDemo {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(5);
AtomicInteger count = new AtomicInteger(0);
for (int i = 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("Final Count is : " + count.get());
}
}
AtomicInteger
實現
閱讀 AtomicInteger
源碼,可以看到如下定義:
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;
說明:
value
- value 屬性使用volatile
修飾,使得對 value 的修改在並發環境下對所有線程可見。valueOffset
- value 屬性的偏移量,通過這個偏移量可以快速定位到 value 字段,這個是實現 AtomicInteger 的關鍵。unsafe
- Unsafe 類型的屬性,它為 AtomicInteger 提供了 CAS 操作。
三、引用類型
Java 數據類型分為 基本數據類型 和 引用數據類型 兩大類(不了解 Java 數據類型划分可以參考: Java 基本數據類型 )。
上一節中提到了針對基本數據類型的原子類,那么如果想針對引用類型做原子操作怎么辦?Java 也提供了相關的原子類:
AtomicReference
- 引用類型原子類AtomicMarkableReference
- 帶有標記位的引用類型原子類AtomicStampedReference
- 帶有版本號的引用類型原子類
AtomicStampedReference
類在引用類型原子類中,徹底地解決了 ABA 問題,其它的 CAS 能力與另外兩個類相近,所以最具代表性。因此,本節只針對AtomicStampedReference
進行說明。
示例:基於 AtomicReference
實現一個簡單的自旋鎖
public class AtomicReferenceDemo2 {
private static int ticket = 10;
public static void main(String[] args) {
threadSafeDemo();
}
private static void threadSafeDemo() {
SpinLock lock = new SpinLock();
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executorService.execute(new MyThread(lock));
}
executorService.shutdown();
}
/**
* 基於 {@link AtomicReference} 實現的簡單自旋鎖
*/
static class SpinLock {
private AtomicReference<Thread> atomicReference = new AtomicReference<>();
public void lock() {
Thread current = Thread.currentThread();
while (!atomicReference.compareAndSet(null, current)) {}
}
public void unlock() {
Thread current = Thread.currentThread();
atomicReference.compareAndSet(current, null);
}
}
/**
* 利用自旋鎖 {@link SpinLock} 並發處理數據
*/
static class MyThread implements Runnable {
private SpinLock lock;
public MyThread(SpinLock lock) {
this.lock = lock;
}
@Override
public void run() {
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 發生的幾率。常用於緩存或者狀態描述這樣的場景。
public class AtomicMarkableReferenceDemo {
private final static String INIT_TEXT = "abc";
public static void main(String[] args) throws InterruptedException {
final AtomicMarkableReference<String> amr = new AtomicMarkableReference<>(INIT_TEXT, false);
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(Math.abs((int) (Math.random() * 100)));
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = 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 問題。
public class AtomicStampedReferenceDemo {
private final static String INIT_REF = "pool-1-thread-3";
private final static AtomicStampedReference<String> asr = new AtomicStampedReference<>(INIT_REF, 0);
public static void main(String[] args) throws InterruptedException {
System.out.println("初始對象為:" + asr.getReference());
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 3; i++) {
executorService.execute(new MyThread());
}
executorService.shutdown();
executorService.awaitTermination(3, TimeUnit.SECONDS);
}
static class MyThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(Math.abs((int) (Math.random() * 100)));
} catch (InterruptedException e) {
e.printStackTrace();
}
final int stamp = 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
使用方式也類似)
public class AtomicIntegerArrayDemo {
private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);
public static void main(final String[] arguments) throws InterruptedException {
System.out.println("Init Values: ");
for (int i = 0; i < atomicIntegerArray.length(); i++) {
atomicIntegerArray.set(i, i);
System.out.print(atomicIntegerArray.get(i) + " ");
}
System.out.println();
Thread t1 = new Thread(new Increment());
Thread t2 = new Thread(new Compare());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Final Values: ");
for (int i = 0; i < atomicIntegerArray.length(); i++) {
System.out.print(atomicIntegerArray.get(i) + " ");
}
System.out.println();
}
static class Increment implements Runnable {
@Override
public void run() {
for (int i = 0; i < atomicIntegerArray.length(); i++) {
int value = atomicIntegerArray.incrementAndGet(i);
System.out.println(Thread.currentThread().getName() + ", index = " + i + ", value = " + value);
}
}
}
static class Compare implements Runnable {
@Override
public void run() {
for (int i = 0; i < atomicIntegerArray.length(); i++) {
boolean swapped = atomicIntegerArray.compareAndSet(i, 2, 3);
if (swapped) {
System.out.println(Thread.currentThread().getName() + " swapped, index = " + i + ", value = 3");
}
}
}
}
}
五、屬性更新器類型
更新器類支持基於反射機制的更新字段值的原子操作。
AtomicIntegerFieldUpdater
- 整型字段的原子更新器。AtomicLongFieldUpdater
- 長整型字段的原子更新器。AtomicReferenceFieldUpdater
- 原子更新引用類型里的字段。
這些類的使用有一定限制:
- 因為對象的屬性修改類型原子類都是抽象類,所以每次使用都必須使用靜態方法
newUpdater()
創建一個更新器,並且需要設置想要更新的類和屬性。 - 字段必須是
volatile
類型的; - 不能作用於靜態變量(
static
); - 不能作用於常量(
final
);
public class AtomicReferenceFieldUpdaterDemo {
static User user = new User("begin");
static AtomicReferenceFieldUpdater<User, String> updater =
AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 0; i < 5; i++) {
executorService.execute(new MyThread());
}
executorService.shutdown();
}
static class MyThread implements Runnable {
@Override
public void run() {
if (updater.compareAndSet(user, "begin", "end")) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 已修改 name = " + user.getName());
} else {
System.out.println(Thread.currentThread().getName() + " 已被其他線程修改");
}
}
}
static class User {
volatile String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public User setName(String name) {
this.name = name;
return this;
}
}
}
參考資料
- 《Java 並發編程實戰》
- 《Java 並發編程的藝術》
- JUC 中的原子類
- http://tutorials.jenkov.com/java-util-concurrent/atomicinteger.html
- http://tutorials.jenkov.com/java-util-concurrent/atomicintegerarray.html
- http://tutorials.jenkov.com/java-util-concurrent/atomicreference.html
- http://tutorials.jenkov.com/java-util-concurrent/atomicstampedreference.htm