CAS操作以及jdk1.8后的優(yōu)化

一、前言

說到CAS之前觉壶,先來看看樂觀鎖與悲觀鎖:

悲觀鎖認為:每個線程在對一數(shù)據(jù)進行操作時脑题,都會有其他線程來并發(fā)修改,所以在獲取數(shù)據(jù)的時候就上鎖來進行操作铜靶,synchronized和lock就是一種悲觀鎖的策略叔遂。也就是先上鎖再操作。

樂觀鎖認為:每個線程在對以數(shù)據(jù)進行操作時争剿,沒有其他線程來并發(fā)修改已艰,這樣就其實是所有線程都去讀取共享區(qū)的數(shù)據(jù),然后在本地工作內(nèi)存操作蚕苇,最后看共享區(qū)的數(shù)據(jù)有無被其他線程更新哩掺。如果沒有則將修改后的數(shù)據(jù)寫入,如果有的話就根據(jù)具體實現(xiàn)具體分析(報錯或者自動重試)涩笤。即直接操作

我們不難得出:
悲觀鎖適合大量寫操作的場景嚼吞,先加鎖可以保證寫操作時數(shù)據(jù)的正確。
樂觀鎖適合大量讀操作的場景蹬碧,不加鎖的特點能夠使其讀操作的性能大大提升舱禽。

二、什么是CAS操作

CAS操作锰茉,全稱Compare and Swap呢蔫,比較并交換。

CAS操作就是一個虛擬機實現(xiàn)的原子操作(一條硬件操作指令,不可被中斷的一個或一系列操作)片吊,功能是將舊值替換為新值绽昏,如果舊值沒有改變則替換成功,否則替換失敗俏脊。

一般使用鎖的時候全谤,線程獲取鎖是一種悲觀鎖策略。即假設每一次在訪問共享資源都會產(chǎn)生沖突爷贫,所以當前線程獲取到鎖的同時就會阻塞其他線程獲取該鎖认然。

CAS操作是一種樂觀鎖策略。它假設每一次在訪問共享資源時都不會產(chǎn)生沖突漫萄,那不沖突就不會阻塞其他線程獲取該鎖卷员,這樣線程就不會出現(xiàn)阻塞停頓狀態(tài)。Java使用CAS來鑒別線程是否出現(xiàn)沖突腾务,出現(xiàn)沖突就重試當前操作直到?jīng)]有沖突為止毕骡。線程只會收到操作失敗的信號并進行原地自旋,并不會阻塞岩瘦。

三未巫、CAS操作的過程

CAS操作離不開這三個值(V, O, N):

  • V:內(nèi)存地址存放的實際值
  • O:舊值
  • N:即將更新的新值

當且僅當VO相同時,即舊值和內(nèi)存中實際存放的值相同启昧,這表明該值沒有被其他線程更改過叙凡,此時CAS通過原子的方式將N賦給V,并返回true密末。這是一個比較+更新操作握爷,是原子操作。如果VO不相同严里,則該值已經(jīng)被其他線程修改饼拍,不能把N賦給V,此時不進行操作田炭,返回false。多個線程使用CAS操作一個變量時漓柑,只有一個線程會成功教硫,并且成功更新,其余會失斄静肌(并不會阻塞其他線程)瞬矩。失敗的線程會重新嘗試,也可以選擇掛起線程锋玲。

synchronized存在線程競爭的情況下會出現(xiàn)線程阻塞和喚醒鎖帶來的性能問題景用,因為這是一種互斥同步(阻塞同步)。而CAS在競爭時如果失敗,會進行一定的嘗試伞插,而并不是單純的進行掛起喚醒操作割粮,因此也叫非阻塞同步。

四媚污、CAS的問題

CAS主要有以下三個問題:

1.ABA問題

CAS會檢查共享內(nèi)存的值有無變化舀瓢,如果我們的共享內(nèi)存值由A變成了B,可是又由B變回來了耗美,此時CAS檢查的時候發(fā)現(xiàn)共享內(nèi)存的值并沒有變化依然為A京髓,但是實際上卻是發(fā)生了變化。如果基本類型倒無所謂商架,引用類型就會有一些問題堰怨。

解決方案:對其進行版本控制,這樣A-B-A就變成1A-2B-3A了蛇摸。Java1.5后atomic包提供的AromicStampedReference來解決ABA問題备图,具體封裝在compareAndSet()中。compareAndSet()首先檢查當前引用和當前標志與預期引用和預期標志是否相等皇型,如果都相等诬烹,則以原子方式將引用值和標志的值設置為給定的更新值。

2.自旋時間過長

CAS是一種非阻塞同步弃鸦,線程不會自己被掛起绞吁,而是不停的嘗試而產(chǎn)生自旋現(xiàn)象(會死循環(huán)),自旋時間過長就會造成CPU很大的性能消耗唬格。
解決方案請看夏慶文

3.只能保證一個共享變量的原子操作

如果對多個共享變量進行操作家破,CAS不能保證其原子性。
解決方案:利用對象整合多個變量购岗,即一個類中的成員就是這幾個變量汰聋,然后對這個對象進行CAS操作,這么做就能保證其原子性喊积。atomic提供了AtomicReference來保證引用對象的原子性

五烹困、jdk1.8對于CAS的優(yōu)化

jdk1.8提供了一個LongAdder類,嘗試使用分段CAS以及自動分段遷移的方式來大幅度替身多線程高并發(fā)執(zhí)行CAS的性能乾吻。

1.分段CAS:

public class LongAdder extends Striped64 implements Serializable 

其繼承的Striped64里面有兩個重要變量:

/**
 * Table of cells. When non-null, size is a power of 2.
 * cell數(shù)組髓梅,大小總是2的冪次方
 */
transient volatile Cell[] cells;
/**
 * Base value, used mainly when there is no contention, but also as
 * a fallback during table initialization races. Updated via CAS.
 * 基本值,主要在沒有爭用的情況下使用绎签,在表的初始化的時候也作為一個基礎值枯饿。通過CAS更新。
 */
transient volatile long base;

如果發(fā)現(xiàn)并發(fā)更新的線程數(shù)量不是很多诡必,就直接給base值進行累加奢方。如果發(fā)現(xiàn)并發(fā)更新的數(shù)量過多,就開始實行分段CAS機制,系統(tǒng)把這些線程分配到不同的cell數(shù)組元素中蟋字。

public void add(long x) {
    Cell[] cs; long b, v; int m; Cell c;
    if ((cs = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        if (cs == null || (m = cs.length - 1) < 0 ||
            (c = cs[getProbe() & m]) == null ||
            !(uncontended = c.cas(v = c.value, v + x)))
            longAccumulate(x, null, uncontended);
    }
}

源碼大概流程就是首先通過CAS進行對base值的更新稿蹲,此時只有一個線程會成功,然后保存進sum愉老。其余的線程進行cell數(shù)組計算下標并分配场绿,每個線程依次的對cell的元素進行累加,最后將base + sum[i] 求出最后的總和嫉入。

看一下LongAdder中的求cell數(shù)組總和的源碼:

public long sum() {
    Cell[] cs = cells;
    long sum = base;
    if (cs != null) {
        for (Cell c : cs)
            if (c != null)
                sum += c.value;
    }
    return sum;
}

假設當前有80個線程進行一變量的自增操作焰盗,cell數(shù)組長度為8,則每一組都有10個線程咒林,每一組對cell數(shù)組的其中一個元素做自增熬拒,最后cell數(shù)組8個元素的值都為10,累加得到80垫竞。這就等于80個線程對i進行了80次自增操作澎粟。

2.自動遷移機制

隨著線程增多,每個cell中分配的線程數(shù)也會增多欢瞪,當其中一個線程操作失敗的時候活烙,它會自動遷移到下一個cell中進行操作,這也就解決了CAS空旋轉(zhuǎn)遣鼓,自旋不停等待的問題啸盏。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市骑祟,隨后出現(xiàn)的幾起案子回懦,更是在濱河造成了極大的恐慌,老刑警劉巖次企,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怯晕,死亡現(xiàn)場離奇詭異,居然都是意外死亡缸棵,警方通過查閱死者的電腦和手機舟茶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堵第,“玉大人稚晚,你說我怎么就攤上這事⌒统希” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵鸳劳,是天一觀的道長狰贯。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么涵紊? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任傍妒,我火速辦了婚禮,結(jié)果婚禮上摸柄,老公的妹妹穿的比我還像新娘颤练。我一直安慰自己,他們只是感情好驱负,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布嗦玖。 她就那樣靜靜地躺著,像睡著了一般跃脊。 火紅的嫁衣襯著肌膚如雪宇挫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天酪术,我揣著相機與錄音器瘪,去河邊找鬼。 笑死绘雁,一個胖子當著我的面吹牛橡疼,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庐舟,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼欣除,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了继阻?” 一聲冷哼從身側(cè)響起耻涛,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瘟檩,沒想到半個月后抹缕,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡墨辛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年卓研,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片睹簇。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡奏赘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出太惠,到底是詐尸還是另有隱情磨淌,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布凿渊,位于F島的核電站梁只,受9級特大地震影響缚柳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜搪锣,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一秋忙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧构舟,春花似錦灰追、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至抡谐,卻和暖如春裁奇,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背麦撵。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工刽肠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人免胃。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓音五,卻偏偏與公主長得像,于是被迫代替她去往敵國和親羔沙。 傳聞我的和親對象是個殘疾皇子躺涝,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355