Java高並發-無鎖
阿新 • • 發佈:2018-04-07
pda 增加 元素 ref tor help 沒有 底層 可能
一、無鎖類的原理
1.1 CAS
CAS算法的過程是這樣:它包含3個參數CAS(V,E,N)。V表示要更新的變量,E表示預期值,N表示新值。僅當V值等於E值時,才會將V的值設為N,如果V值和E值不同,則說明已經有其他線程做了更新,則當前線程什麽都不做。最後,CAS返回當前V的真實值 。CAS操作是抱著樂觀的態度進行的,它總是認為自己可以成功完成操作。當多個線程同時使用CAS操作一個變量時,只有一個會勝出,並成功更新,其余均會失敗。失敗的線程不會被掛起,僅是被告知失敗,並且允許再次嘗試,當然也允許失敗的線程放棄操作。基於這樣的原理,CAS操作即使沒有鎖,也可以發現其他線程對當前線程的幹擾,並進行恰當的處理。
1.2 CPU指令
二、無鎖類的使用
2.1 AtomicInteger
概述
java.util.concurrent.atomic
public class AtomicInteger extends Number implements java.io.Serializable
主要接口
public final int get() // 獲取當前值
public final void set(int newValue) // 設置當前值
public final int getAndSet(int newValue) // 設置新值,並返回舊值
public final boolean compareAndSet (int expect, int u) // 如果當前值為expect,則設置為u
public final int getAndIncrement() // 當前值加1,返回舊值,類似於i++
public final int getAndDecrement() // 當前值減1,返回舊值,類似於i--
public final int getAndAdd(int delta) // 當前值增加delta,返回舊值
public final int incrementAndGet() // 當前值加1,返回新值,類似於++i
public final int decrementAndGet() // 當前值減1,返回新值,類似於--i
public final int addAndGet(int delta) // 當前值增加delta,返回新值
主要接口的實現
// 內部定義了一個value,AtomicInteger只是對它的封裝
private volatile int value;
compareAndSet
valueOffset是偏移量
舉例
2.2 Unsafe
概述
非安全的操作,比如:根據偏移量設置值
park() 把線程停下來
底層的CAS操作
非公開API,在不同版本的JDK中,可能有較大差異
主要接口
// 獲得給定對象偏移量上的int值
public native int getInt(java.lang.Object arg0, long arg1);
// 設置給定對象像偏移量上的int值
public native void putInt(java.lang.Object arg0, long arg1, int arg2);
// 獲得字段在對象中的偏移量
public native long objectFieldOffset(Field f);
// 設置給定對象的int值,使用volatile語義
public native void putIntVolatile(Object o, long offset, int x);
// 獲得給定對象的int值,使用volatile語義
public native void getIntVolatile(Object o, long offset);
// 和putIntVolatile()一樣,但是它要求被操作字段就是volatile類型的
public native void putOrderedInt(Object o, long offset, int x);
2.3 AtomicReference
概述
對引用進行修改
是一個模板類,抽象化了數據類型
主要接口
get()
set(V)
compareAndSet()
getAndSet(V)
舉例
2.4 AtomicStampedReference
概述
解決ABA問題
一個變量初始值為A
線程1讀取變量 00:00
線程2讀取變量 00:03
線程2將變量修改為B 00:05
線程3讀取變量 00:06
線程3將變量修改為A 00:08
線程1根據變量做計算 00:10
線程1將變量修改為C 00:12 定稿前變量值是A,其實該變量已經經歷了ABA的變化
主要接口
// 比較設置 參數依次為:期望值 寫入新值 期望時間戳 新時間戳
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp)
// 獲得當前對象引用
public V getReference()
// 獲得當前時間戳
public int getStamp()
// 設置當前對象引用和時間戳
public void set(V newReference, int newStamp)
舉例
public class AtomicStampedReferenceDemo {
static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 0);
public static void main(String[] args) {
// 模擬3個充錢線程,余額小余20時充錢且只充一次
for (int i = 0; i < 3; i++) {
final int timestamp = money.getStamp();
new Thread() {
public void run() {
while (true) {
Integer m = money.getReference();
if (m < 20) {
if (money.compareAndSet(m, m + 20, timestamp, timestamp + 1)) {
System.out.println("余額小於20元,充值成功,余額:" + money.getReference());
}
} else {
// 余額大於20,無需充值
break;
}
}
}
}.start();
}
// 模擬一個消費線程
new Thread() {
public void run() {
// 消費10次,余額大於10元時才能消費
for (int i = 0; i < 10; i++) {
while (true) {
int timestamp = money.getStamp();
Integer m = money.getReference();
if (m > 10) {
if (money.compareAndSet(m, m - 10, timestamp, timestamp + 1)) {
System.out.println("成功消費10元,余額:" + money.getReference());
break;
}
} else {
System.out.println("沒有足夠的余額");
break;
}
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
}.start();
}
}
2.5 AtomicIntegerArray
概述
支持無鎖的數組
主要接口
// 獲得數組第i個下標的元素
public final int get(int i)
// 獲得數組的長度
public final int length()
// 將數組第i個下標設置為newValue,並返回舊的值
public final int getAndSet(int i, int newValue)
// 進行CAS操作,如果第i個下標的元素等於expect,則設置為update,設置成功返回true
public final boolean compareAndSet(int i, int expect, int update)
// 將第i個下標的元素加1
public final int getAndIncrement(int i)
// 將第i個下標的元素減1
public final int getAndDecrement(int i)
// 將第i個下標的元素增加delta(delta可以是負數)
public final int getAndAdd(int i, int delta)
舉例
2.6 AtomicIntegerFieldUpdater
概述
讓普通變量也享受原子操作
主要接口
// 工廠方法
AtomicIntegerFieldUpdater.newUpdater()
incrementAndGet()
說明
1. Updater只能修改它可見範圍內的變量。因為Updater使用反射得到這個變量。如果變量不可見,就會出錯。比如如果score聲明為private,就是不可行的。
2. 為了確保變量被正確讀取,它必須是volatile類型的。如果我們原有代碼中未聲明這個類型,那麽簡單聲明一個就行,這不會引起什麽問題。
3. 由於CAS操作會通過對象實例中的偏移量直接進行賦值,因此它不支持static字段(Unsafe.objectFieldOffset()不支持靜態變量)
舉例
三、無鎖算法
3.1 Vector實現
add方法
說明
數組實現
modCount++ 記錄被修改的次數
ensureCapacityHelper(elementCount+1) 做容量檢查
擴容
3.2 無鎖的Vector實現
Java高並發-無鎖