CAS
在JDK 5之前Java語(yǔ)言是靠synchronized關(guān)鍵字保證同步的茴迁,這會(huì)導(dǎo)致有鎖(后面的章節(jié)還會(huì)談到鎖)。
鎖機(jī)制存在以下問(wèn)題:
(1)在多線程競(jìng)爭(zhēng)下俺亮,加鎖沥邻、釋放鎖會(huì)導(dǎo)致比較多的上下文切換和調(diào)度延時(shí)平项,引起性能問(wèn)題赫舒。
(2)一個(gè)線程持有鎖會(huì)導(dǎo)致其它所有需要此鎖的線程掛起。
(3)如果一個(gè)優(yōu)先級(jí)高的線程等待一個(gè)優(yōu)先級(jí)低的線程釋放鎖會(huì)導(dǎo)致優(yōu)先級(jí)倒置闽瓢,引起性能風(fēng)險(xiǎn)接癌。
volatile是不錯(cuò)的機(jī)制,但是volatile不能保證原子性扣讼。因此對(duì)于同步最終還是要回到鎖機(jī)制上來(lái)缺猛。
獨(dú)占鎖是一種悲觀鎖,synchronized就是一種獨(dú)占鎖,會(huì)導(dǎo)致其它所有需要鎖的線程掛起荔燎,等待持有鎖的線程釋放鎖耻姥。而另一個(gè)更加有效的鎖就是樂(lè)觀鎖。所謂樂(lè)觀鎖就是有咨,每次不加鎖而是假設(shè)沒(méi)有沖突而去完成某項(xiàng)操作咏闪,如果因?yàn)闆_突失敗就重試,直到成功為止摔吏。
CAS 操作
上面的樂(lè)觀鎖用到的機(jī)制就是CAS,Compare and Swap纵装。
CAS有3個(gè)操作數(shù)征讲,內(nèi)存值V,舊的預(yù)期值A(chǔ)橡娄,要修改的新值B诗箍。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B挽唉,否則什么都不做滤祖。
非阻塞算法 (nonblocking algorithms)
一個(gè)線程的失敗或者掛起不應(yīng)該影響其他線程的失敗或掛起的算法。
現(xiàn)代的CPU提供了特殊的指令瓶籽,可以自動(dòng)更新共享數(shù)據(jù)匠童,而且能夠檢測(cè)到其他線程的干擾,而 compareAndSet() 就用這些代替了鎖定塑顺。
拿出AtomicInteger來(lái)研究在沒(méi)有鎖的情況下是如何做到數(shù)據(jù)正確性的汤求。
private volatile int value;
首先毫無(wú)以為,在沒(méi)有鎖的機(jī)制下可能需要借助volatile原語(yǔ)严拒,保證線程間的數(shù)據(jù)是可見(jiàn)的(共享的)扬绪。
這樣才獲取變量的值的時(shí)候才能直接讀取。
public final int get() {
? return value;
? }
然后來(lái)看看++i是怎么做到的裤唠。
public final int incrementAndGet() {
? for (;;) {
? int current = get();
? int next = current + 1;
? if (compareAndSet(current, next))
? return next;
? }
}
在這里采用了CAS操作挤牛,每次從內(nèi)存中讀取數(shù)據(jù)然后將此數(shù)據(jù)和+1后的結(jié)果進(jìn)行CAS操作,如果成功就返回結(jié)果种蘸,否則重試直到成功為止墓赴。
而compareAndSet利用JNI來(lái)完成CPU指令的操作。
public final boolean compareAndSet(int expect, int update) {
? return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
? }
整體的過(guò)程就是這樣子的劈彪,利用CPU的CAS指令竣蹦,同時(shí)借助JNI來(lái)完成Java的非阻塞算法。其它原子操作都是利用類似的特性完成的沧奴。
而整個(gè)J.U.C都是建立在CAS之上的痘括,因此對(duì)于synchronized阻塞算法,J.U.C在性能上有了很大的提升。參考資料的文章中介紹了如果利用CAS構(gòu)建非阻塞計(jì)數(shù)器纲菌、隊(duì)列等數(shù)據(jù)結(jié)構(gòu)挠日。
CAS看起來(lái)很爽,但是會(huì)導(dǎo)致“ABA問(wèn)題”翰舌。
CAS算法實(shí)現(xiàn)一個(gè)重要前提需要取出內(nèi)存中某時(shí)刻的數(shù)據(jù)嚣潜,而在下時(shí)刻比較并替換,那么在這個(gè)時(shí)間差類會(huì)導(dǎo)致數(shù)據(jù)的變化椅贱。
比如說(shuō)一個(gè)線程one從內(nèi)存位置V中取出A懂算,這時(shí)候另一個(gè)線程two也從內(nèi)存中取出A,并且two進(jìn)行了一些操作變成了B庇麦,然后two又將V位置的數(shù)據(jù)變成A计技,這時(shí)候線程one進(jìn)行CAS操作發(fā)現(xiàn)內(nèi)存中仍然是A,然后one操作成功山橄。盡管線程one的CAS操作成功垮媒,但是不代表這個(gè)過(guò)程就是沒(méi)有問(wèn)題的。如果鏈表的頭在變化了兩次后恢復(fù)了原值航棱,但是不代表鏈表就沒(méi)有變化睡雇。因此前面提到的原子操作AtomicStampedReference/AtomicMarkableReference就很有用了。這允許一對(duì)變化的元素進(jìn)行原子操作饮醇。
參考資料:
(2)流行的原子