📦 本文以及示例源碼已歸檔在 javacorehtml
保證線程安全是 Java 併發編程必需要解決的重要問題。Java 從原子性、可見性、有序性這三大特性入手,確保多線程的數據一致性。java
Lock
、sychronized
)來對共享數據作互斥同步,這樣在同一個時刻,只有一個線程能夠執行某個方法或者某個代碼塊,那麼操做必然是原子性的,線程安全的。互斥同步最主要的問題是線程阻塞和喚醒所帶來的性能問題。 volatile
是輕量級的鎖(天然比普通鎖性能要好),它保證了共享變量在多線程中的可見性,但沒法保證原子性。因此,它只能在一些特定場景下使用。 Unsafe
類)來實現非阻塞同步(也叫樂觀鎖)。並基於 CAS ,提供了一套原子工具類。 原子變量類 比鎖的粒度更細,更輕量級,而且對於在多處理器系統上實現高性能的併發代碼來講是很是關鍵的。原子變量將發生競爭的範圍縮小到單個變量上。git
原子變量類至關於一種泛化的 volatile
變量,可以支持原子的、有條件的讀/改/寫操做。github
原子類在內部使用 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;
}
}
}複製代碼