Volitale
并發(fā)中,鎖主要提供兩種特性:互斥 與 可見
并發(fā)情況下,一個線程先到來,給對象上鎖,其他線程只能在后面排隊,這種互斥帶給我們的是操作的 原子性,即在并發(fā)的情況下,一個操作不會受其他線程的影響
另外,每個線程在讀取變量時,自己都會緩存一份,也就是說,可能下次讀取操作便不會經(jīng)過內(nèi)存,而是直接取緩存,當然,這在并發(fā)條件下,線程共享的變量會有問題是理所當然的,如果每個線程在訪問一個變量時,每次都從內(nèi)存中重新讀取值,那么我們認為這個變量對于每個線程來說具有 可見性
當然,若你不需要互斥,只需要保證可見性,Java 提供了一種更輕量的語法 volitale,經(jīng)過 volitale 修飾的變量,可以保證:
- 可見性
- 防止指令重排
在執(zhí)行代碼時,代碼順序為 A -> B -> C,CPU 為了執(zhí)行效率,可能會將其順序打亂,當然,為什么我們在單線程條件下執(zhí)行卻是有序的,原因是指令重排遵循 as-if-serial 語義,即無論如何重排,都不會影響結(jié)果,就像如果 C 依賴于 A 和 B,那么總是會在 A 和
B 都執(zhí)行完后,才會執(zhí)行 A
顯而易見的是 volitale 并不支持原子性,就像 i++ 的執(zhí)行過程:
- 從內(nèi)存中取出 i
- 將 i 加 1
- 再將結(jié)果賦值給 i
volitale 有一種寫法叫 讀 - 寫鎖,它本身是矛盾的,在我們使用這種寫法時,我們認為讀取應(yīng)該可以是不嚴謹?shù)?,如果超越了該模式的最基本?yīng)用,結(jié)合這兩個競爭的同步機制將變得非常困難
private volatile int value;
public int getValue() { return value; }
public synchronized int increment() {
return value++;
}
Unsafe
正如它的名字一樣,Unsafe 是一個非常不安全的類
并發(fā)包下 Unsafe 提供以下功能:
- 直接操作內(nèi)存地址
- 恢復(fù)與掛起線程
- 開放匯編、CPU級別的 CAS [1] 操作
我們可以認為,Jdk 并發(fā)包是在 Unsafe 下建立的,它的構(gòu)造函數(shù)是被類加載器封死的,外包下可以通過反射獲取,但使用 Unsafe 類,請務(wù)必對它十分清楚
Atomic 系列類
如果我們想為自己的對象原子化,AtomicReference 是一種很好的選擇,AtomicReference 內(nèi)部已經(jīng)維護好了一個 Unsafe、內(nèi)存偏移地址,也開放了一些原子操作的入口
public class AtomicReference<V> implements java.io.Serializable {
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile V value;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicReference.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
// ...
}
如果我們想為自己的對象數(shù)組原子化,AtomicReferenceArray 是一種很好的選擇
如果我們想為自己的數(shù)字變量原子化,可以選擇一系列的 AtomicInteger,AtomicDouble ...
它們的實現(xiàn)大致相同,內(nèi)部都是基于 Unsafe 構(gòu)建的,我們在這里就不一一詳細討論了