??比較交換是一種無(wú)鎖的并發(fā)策略张吉,使用這種方式可以避免鎖競(jìng)爭(zhēng)帶來(lái)的系統(tǒng)開(kāi)銷,以及線程間頻繁調(diào)度帶來(lái)的系統(tǒng)開(kāi)銷伦忠,因此具有比鎖更好的性能昆码。CAS算法如下:它包含3個(gè)參數(shù)CAS(V,E,N),V代表要更新的變量脓匿,E代表預(yù)期值陪毡,N代表新值。僅當(dāng)V值等于E時(shí)绊起,才會(huì)將V設(shè)置為N虱歪,如果V不等于N师枣,說(shuō)明有其他線程對(duì)V進(jìn)行了操作践美,則當(dāng)前線程什么也不做。因此每一次只有一個(gè)線程會(huì)操作成功兴革,其余的均會(huì)失敗杂曲,失敗的線程并不會(huì)被掛起,只是被告知失敗棚饵,并會(huì)繼續(xù)嘗試蟹地,直到操作成功。
??Java并發(fā)包中提供了一個(gè)atomic包分别,里面實(shí)現(xiàn)了一些CAS操作的線程安全的類型耘斩。
1.無(wú)鎖的線程安全整數(shù)(AtomicInteger)內(nèi)部實(shí)現(xiàn)分析(基于jdk1.8):
private volatile int value;
value代表AtomicInteger的值括授,用volatile修飾薛夜,保證了線程間的可見(jiàn)性
private static final long valueOffset;
valueOffset保存了value字段在AtomicInteger對(duì)象上的偏移量梯澜,通過(guò)valueOffset可以定位到AtomicInteger對(duì)象value字段的位置晚伙,其實(shí)現(xiàn)如下:
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
常用方法舉例:
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
//當(dāng)前值加1,并返回加1前的值
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
//當(dāng)前值加delta民傻,并返回先前的值
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
//當(dāng)前值減1,并返回先前的值
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
當(dāng)前值加delta,并返回加delta之前的值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
如果當(dāng)前值與預(yù)期值(expect)相等青责,則將當(dāng)前值更新為新值(update)
public float floatValue() {
return (float)get();
}
將整型轉(zhuǎn)化為float型
該類中的方法基本都調(diào)用unsafe實(shí)現(xiàn)脖隶,而unsafe中的方法底層采用c實(shí)現(xiàn),比如:
public native boolean compareAndSwapInt(Object obj, long offset,int expect,int update);
類似的實(shí)現(xiàn)還有AtomicLong构蹬、AtomicBoolean、AtomicReference藻烤,此處不再贅述
2.存在的問(wèn)題:
??使用CAS操作無(wú)法判斷當(dāng)前數(shù)據(jù)的狀態(tài)之众,當(dāng)某個(gè)線程修改了value棺禾,下一個(gè)線程又把它修改回上一個(gè)線程修改前的值,那么之后線程并不知道它曾被修改過(guò)悬襟。即常說(shuō)的ABA問(wèn)題脊岳。
??針對(duì)這種情況,JDK提供了AtomicStampedReference類亿驾,其內(nèi)部同時(shí)維護(hù)對(duì)象值和一個(gè)時(shí)間戳莫瞬。當(dāng)AtomicStampedReference對(duì)象的值被修改時(shí),除了要更新對(duì)象值之外旁振,還要同時(shí)更新時(shí)間戳规求。當(dāng)AtomicStampedReference設(shè)置對(duì)象值時(shí),對(duì)象值和時(shí)間戳都要滿足期望值丛塌,寫入才會(huì)成功赴邻。因此即使對(duì)象值被寫會(huì)原值奸焙,只要時(shí)間戳發(fā)生變化与帆,就能防止不恰當(dāng)?shù)膶懭搿?br>
內(nèi)部實(shí)現(xiàn)分析:
private volatile Pair<V> pair;
pair保存了對(duì)象值以及時(shí)間戳,Pair類源碼如下:
private static class Pair<T> {
final T reference; //對(duì)象值
final int stamp; //用一個(gè)整數(shù)表示狀態(tài)值袄秩,即時(shí)間戳
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static <T> Pair<T> of(T reference, int stamp) {
return new Pair<T>(reference, stamp);
}
}
以 compareAndSet方法為例:
/**
* Atomically sets the value of both the reference and stamp
* to the given update values if the
* current reference is {@code ==} to the expected reference
* and the current stamp is equal to the expected stamp.
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
當(dāng)期望值與當(dāng)前值相等,且期望時(shí)間戳和當(dāng)前時(shí)間戳相等時(shí)箱沦,才會(huì)寫入新值,并同時(shí)更新時(shí)間戳疆前,casPair方法實(shí)現(xiàn)如下:
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
3.不安全類(unsafe)
??Java不能直接訪問(wèn)操作系統(tǒng)底層,而是通過(guò)本地方法來(lái)訪問(wèn)胸完,其中封裝了一些類似指針的操作赊窥,使用unsafe類可以分配內(nèi)存锨能,釋放內(nèi)存熄阻,類中提供3個(gè)本地方法allocateMemory秃殉、reallocateMemory、freeMemory分別用于分配內(nèi)存巧颈,擴(kuò)充內(nèi)存和釋放內(nèi)存
unsafe主要方法如下:
//獲得給定對(duì)象偏移量上的int值
private native int getInt(Object o,long offset);
//設(shè)置給定對(duì)象偏移量上的int值
private native int putInt(Object o,long offset,int x);
//獲得字段在對(duì)象上的偏移量
private native long objectFieldOffset(Field f);
//設(shè)置給定對(duì)象的int值砸泛,使用volatile語(yǔ)義
private native void putIntVolatile(Object o,long offset,int x);
//獲得的給定對(duì)象的int值唇礁,使用volatile語(yǔ)義
private native int getIntVolatile(Object o,long offset);
//和putIntVolatile一樣,不過(guò)它要求被操作字段就是volatile類型的
private native void putOrderedInt(Object o,long offset,int x);
??JDK開(kāi)發(fā)人員并不希望大家使用這個(gè)類琢融,獲得unsafe實(shí)例的方法是調(diào)用其工廠方法getUnsafe()漾抬,其實(shí)現(xiàn)如下:
public static Unsafe(){
Class cc=Reflection.getCallerClass();
if(cc.getClassLoader()!=null)
throw new SecurityException("Unsafe");
return theUnsafe;
}
??調(diào)用該方法時(shí),它會(huì)檢查調(diào)用該方法的類平绩,如果這個(gè)類的ClassLoader不為null捏雌,就直接拋出異常来累,拒絕訪問(wèn)嘹锁。