Java并發程式設計之無鎖
在談論無所之前先來看看樂觀派和悲觀派。對于樂觀派而言,他們總認為事情總會朝着好的方向發展,總認為幾乎不會壞事,我已可以随意的去做事。但是對于悲觀派來說,他們認為出錯是一種常态,是以無論事情大小都會考慮的面面俱到,滴水不漏。
在兩種派别對應在并發中就是加鎖和無鎖,也就是說加鎖是一種悲觀的政策,而無鎖是一種樂觀的政策。對于鎖,如果有多個線程同僚通路一個臨界資源,甯可犧牲性能讓線程等待,也不會讓一個線程不加鎖通路臨界資源。對于無鎖,它會假定對資源的通路是沒有沖突的。也就是多個線程對臨界資源的通路是沒有沖突的。既然沒有沖突,那麼就不需要等待,所有的線程都可以不需要等待的執行下去。如果遇到了沖突,怎麼辦?這裡無鎖政策使用了一種稱為CAS的技術來保證線程執行的安全性。下面我們來具體讨論一下CAS。
無鎖解決沖突的辦法:CAS
CAS的全稱是Compare And Swap即比較和交換。
CAS算法的過程是這樣的:它包含三個參數的變量CAS(V, E, N)。作用如下:
- V 表示要更新的值。
- E 表示預期的值。
- N 表示新值。
僅當V值等于E值的,才會将V值設定為N值,如果V值和E值不同,則說明已經有其他線程做了更新,目前線程什麼都不做。CVS傳回的是目前V的真實值。
CAS是樂觀派,總認為自己可以操作成功。當多個線程同時使用CAS來給一個變量設定時,隻有一個會成功,其它的都會失敗,但是CAS很樂觀,失敗了就失敗了,可以再次嘗試。
Java中的指針:Unsafe類
What? Java中也有指針。Unsfae類就像它的名字一樣,不安全,裡面有一些向C語言的指針一樣直接操作記憶體的方法。并且官方也不推薦直接使用Unsafe類。但是CAS的實作用到了這個類。
下面來看看Unsafe中的一些方法:
// 配置設定記憶體
public native long allocateMemory(long var1);
// 重新配置設定記憶體
public native long reallocateMemory(long var1, long var3);
// 拷貝記憶體
public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7);
// 釋放記憶體
public native void freeMemory(long var1);
// 擷取起始位址
public native long getAddress(long var1);
// 擷取作業系統記憶體頁大小
public native int pageSize();
.....
Unsafe中有兩個方法可以将線程挂起和恢複,如下:
// 線程調用方法,将線程挂起
public native void unpark(Object var1);
// 線程恢複
public native void park(boolean var1, long var2);
Unsafe中的關于CAS的操作:
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);
CAS的操作的核心實作是這三個方法。
三個方法的參數都類似,分别為:
- CAS需要更改變量的對象;
- 對象記憶體的偏移量;
- 期望值
- 需要設定的值
其中偏移量可以通過Unsafe類中的objectFieldOffset()方法擷取到,這些方法如下:
因為int,long,boolean類型的相關操作不是原子性的,是以JDK在1.5之後提供了atomic包(具體在
java.util.concurrent.atomic
中)來将這些操作變成原子操作。
下面的圖檔中的是atomic下提供的原子操作的類:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CXsJlMiNnQIVGbKhVWw40MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DMyYzNwkTN1EzNxATM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
這裡以AtomicInteger來進行分析:
public class AtomicInteger extends Number implements java.io.Serializable {
// 執行個體化Unsafe
private static final Unsafe unsafe = Unsafe.getUnsafe();
// valueOffset變量儲存的是value中的偏移量,這一點可以在下面的static初始化塊中可以看出
private static final long valueOffset;
static {
try {
// 擷取value的偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
/**
* 通過int值執行個體化一個AtomicInteger對象
*/
public AtomicInteger(int initialValue) {
value = initialValue;
}
/**
* 執行個體化一個AtomicInteger對象,其初始值為0
*/
public AtomicInteger() {
}
// 擷取目前值
public final int get() {
return value;
}
// 設定目前值
public final void set(int newValue) {
value = newValue;
}
// 延遲設值
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
// 設定新值擷取舊值
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
// CAS操作,把目前值和預期值做比較,相當時設定新值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// 目前值加1,傳回舊值
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// 目前值-1,傳回舊值
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
// 目前值加上delta,然後傳回舊值
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
// 目前值加1,然後傳回
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// 目前值減1,傳回傳回
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
// 目前值加上delta,然後傳回
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
// ...
}
其中的大部分方法都直接或者間接使用了CAS來保證安全。
除了上面的對于基本變量的Atomic類,還有關于普通對象引用的Atmoic類。
AtomicReference 無鎖的引用
AtomicReference和AtmoicInteger非常類似,不同之處就是AtmoicInteger是對整數的封裝,而AtmoicReference是對普通對象的引用,也就是它可以保證在修改對象引用時線程的安全性。
CAS中有一個很重要的問題ABA。CAS比較的是對象中的值和期望值,但是有可能在你擷取到目前對象的資料後,在準備修改為新值之前,對象的值被其他線程連續修改兩次,而且經過這兩次修改之後,對象有恢複為舊值。這樣,前後的結果看似沒有被改過,但是其實已經被修改了2次。過程如下圖:
一般來說,發生這種事情的可能性很小。而且即使發生了也不會有什麼影響,比如,一個數字,被修改一次後,在修改回去,不會對CAS産生什麼影響。
但是有時候在一些具體問題中這種情況就有可能發生。是以在JDK中提供了AtomicStampedReference來解決這種問題。
AtomicStampedReference
AtomicStampedReference在内部維護了一個時間戳。當AtmoicStampedReference對應的數字被修改時,除了更新資料本身,還必須更新時間戳。當AtomicStampedReference設定對象值時,對象和時間戳都必須滿足期望值,才會寫入成功。通過維護時間戳能有效的防止ABA問題。
AtmoicStampedReference的幾個API在Atmomic的基礎上新增了幾個有關時間戳的資訊。
// 參數為:期望值,新值,期望時間戳,新的時間戳
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp);
// 擷取索引
public V getReference()
// 擷取時間戳
public int getStamp()
// 設定目前對象引用和時間戳
public void set(V newReference, int newStamp)