日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 >

从原子性挖到CAS

發(fā)布時(shí)間:2025/5/22 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从原子性挖到CAS 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

之前在分析volatile關(guān)鍵字的時(shí)候有提到了volatile不能保證原子性,而且書上提到可以通過加鎖(synchronized和JUC中的原子類)來保證原子性。這邊炒一波冷飯,對(duì)于synchronized我也在上篇討論過它在同一時(shí)間只能允許一個(gè)線程去訪問一段特定的代碼,從而保護(hù)一些變量或者數(shù)據(jù)不會(huì)被其他線程所修改來實(shí)現(xiàn)原子性。至于為什么,上篇中也有解釋這里就不再贅述了。對(duì)于Java JUC包中的原子類,它們的底層就是通過CAS來實(shí)現(xiàn)對(duì)變量的原子操作。在平常看書或者看其他大神博客的過程中,CAS這個(gè)概念經(jīng)常會(huì)被提起,既然不明白為什么不進(jìn)行了解一番呢?

CAS(compare and swap)是什么?

(宏觀來說)在計(jì)算機(jī)科學(xué)中,比較和交換(CAS)是多線程中用于實(shí)現(xiàn)同步的原子指令(來自Wikipedia)。(回到現(xiàn)實(shí)來說)CAS中包含三個(gè)參數(shù),內(nèi)存中的值V、期望值A(chǔ)和要修改的值B。 拿期望值A(chǔ)與內(nèi)存中的值V進(jìn)行比較,如果相同,則用B替換內(nèi)存中的值V,否則什么都不做。如果單線程的情況下,所有的操作都是一個(gè)線程來完成,就不需要考慮其他的問題。但是如果是多線程的話,同時(shí)訪問內(nèi)存就會(huì)有不一樣的情況。

如果A、B線程能自覺的排隊(duì)去訪問主內(nèi)存中的值就沒這篇文章什么事了。 在某一時(shí)刻,線程A、B同時(shí)想去修改主內(nèi)存中的共享變量。根據(jù)CAS原則有3個(gè)步驟要走,讀值比較替換。它們同時(shí)讀到了內(nèi)存中的值V,并將值以期望值A(chǔ)的形式存到自己的工作內(nèi)存(根據(jù)Java內(nèi)存模型,每個(gè)線程有自己的工作內(nèi)存來對(duì)變量進(jìn)行操作)中。接著就是線程進(jìn)行寫值操作的時(shí)候了,內(nèi)存中的值只有一個(gè),若想操作它總要有個(gè)先來后到。若A線程眼疾手快搶占先機(jī)(假設(shè)這時(shí)并沒有第三個(gè)線程改變內(nèi)存中的值V),用期望值A(chǔ)與內(nèi)存中的值V比較,發(fā)現(xiàn)一致然后將自己的更新值B替換主內(nèi)存的值V。然后線程B來了,在做比較的時(shí)候發(fā)現(xiàn)自己的期望值A(chǔ)與內(nèi)存中的值V不相等,只能悻悻而歸。

CAS的優(yōu)點(diǎn)?

上面說到,volatile關(guān)鍵字不能保證原子性,開發(fā)過程中可以通過加鎖的形式來保證。這里要鞭尸一波synchronized關(guān)鍵字,多線程的情況下,它能保證原子性。但是當(dāng)一個(gè)線程獲得鎖后,其他線程就被掛起,當(dāng)獲得鎖的線程釋放鎖后其他線程才能重新去競(jìng)爭(zhēng)鎖。每一次線程的阻塞和喚醒都需要操作系統(tǒng)的介入,需要在用戶態(tài)和和內(nèi)核態(tài)之間切換,而這種切換會(huì)消耗大量的系統(tǒng)資源。而使用CAS基于指令來實(shí)現(xiàn),不需要進(jìn)入內(nèi)核或者切換線程,所以性能上會(huì)比synchronized關(guān)鍵字要好。

CAS的缺陷?

上面講了它的有點(diǎn),現(xiàn)在嘴臭一波。

  • CPU開銷大
    在并發(fā)量大的情況下,CAS自旋的概率會(huì)變大。若多個(gè)線程反復(fù)的去嘗試更新一個(gè)變量卻一直不成功,會(huì)一直循環(huán)等待重試,直到耗盡CPU分配給該線程的時(shí)間片,對(duì)CPU造成巨大的壓力。
  • 不能保證代碼塊的一致性
    CAS機(jī)制只能保證一個(gè)變量的原子性操作,多個(gè)變量時(shí)還是只能使用synchronized關(guān)鍵字。
  • ABA問題
    在線程讀取變量和替換變量值的過程中存在一定的時(shí)間差,在這個(gè)時(shí)間差中內(nèi)存中的變量值可能從A變成B再變成A,當(dāng)前線程無法判斷當(dāng)前V值是否發(fā)生變化。對(duì)于如何解決這個(gè)ABA的問題,《并發(fā)編程藝術(shù)》中給出為每一個(gè)變量添加標(biāo)識(shí),一旦對(duì)變量的值修改后,對(duì)標(biāo)識(shí)也進(jìn)行操作。在每次CAS比較的過程中,同時(shí)去比較標(biāo)識(shí)的值來判斷當(dāng)前的V值是否發(fā)生變化。Java提供了AtomicStampedReference來解決ABA的問題,它通過創(chuàng)建Pair內(nèi)部對(duì)象來維護(hù)標(biāo)記的引用。源碼部分也還好理解的, 當(dāng)前的引用和標(biāo)識(shí)與預(yù)期的引用和標(biāo)識(shí)相等,并且更新后的引用和標(biāo)志與當(dāng)前的引用和標(biāo)志相等則直接返回true,否則通過生成一個(gè)新的Pair對(duì)象與當(dāng)前Pair進(jìn)行CAS替換。
  • public class AtomicStampedReference<V> {private static class Pair<T> {final T reference;final int stamp;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);}}public boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) {Pair<V> current = pair;returnexpectedReference == current.reference &&expectedStamp == current.stamp &&((newReference == current.reference &&newStamp == current.stamp) ||casPair(current, Pair.of(newReference, newStamp)));} } 復(fù)制代碼
    CAS具體的使用場(chǎng)景?

    這里也就拿上面說到的JUC下原子操作類來舉例子。原子操作類是在java.util.concurrent.atomic包下一系列以Atomic開頭的包裝類。AtomicInteger也可以保證共享變量在多線程環(huán)境下是線程安全的。

    AtomicInteger源碼

    public class AtomicInteger extends Number implements java.io.Serializable {private static final Unsafe unsafe = Unsafe.getUnsafe();// value成員屬性的內(nèi)存地址相對(duì)于對(duì)象內(nèi)存地址的偏移量private static final long valueOffset;static {try {valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));} catch (Exception ex) { throw new Error(ex); }}private volatile int value;public final int get() {return value;}public final int getAndIncrement() {return unsafe.getAndAddInt(this, valueOffset, 1);} } 復(fù)制代碼

    在調(diào)試AtomicInteger源碼時(shí)發(fā)現(xiàn),不同的AtomicInteger的對(duì)象中valueOffset的值都是相同的,由此產(chǎn)生疑問,最后查閱資料發(fā)現(xiàn)valueOffSet為value成員屬性的內(nèi)存地址相對(duì)于對(duì)象內(nèi)存地址的偏移量。

    Unsafe是CAS的核心類,Java方法無法直接訪問底層的系統(tǒng),需要通過本地方法(native)來訪問。Unsafe可以直接操作特定的內(nèi)存數(shù)據(jù)。value值用volatile關(guān)鍵字修飾,保證了變量在多線程操作中的內(nèi)存可見性。

    馬后炮

    對(duì)于線程、鎖這邊的知識(shí)點(diǎn)很多都是互相關(guān)聯(lián)的,很難對(duì)這邊這么多的概念進(jìn)行一個(gè)系統(tǒng)的羅列。最近寫的幾篇都是偏理論的而且大部分還是書上的內(nèi)容,寫的還是比較虛,不過每次在推敲著寫的時(shí)候會(huì)發(fā)現(xiàn)很多不起眼的其他相關(guān)知識(shí),如果感覺有用還是會(huì)貼到文章的句子中,還是希望繼續(xù)寫下去的時(shí)候能多有一些自己的想法。

    最后還是那句話,學(xué)習(xí)的最終目的并不是為了面試,面試只是一個(gè)激勵(lì)學(xué)習(xí)的動(dòng)機(jī)。把握面試題,享受學(xué)習(xí)新知識(shí)的樂趣。

    參考:

    《并發(fā)編程的藝術(shù)》

    總結(jié)

    以上是生活随笔為你收集整理的从原子性挖到CAS的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。