欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java多線程之并發(fā)編程的基石CAS機(jī)制詳解

 更新時(shí)間:2021年09月16日 11:31:58   作者:Java后端何哥  
這篇文章主要介紹了java并發(fā)編程之cas詳解,涉及cas使用場(chǎng)景和cas用作原子操作等內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下

前言: synchronized保證了線程安全,但是在某些情況下,卻不是一個(gè)最優(yōu)選擇,關(guān)鍵在于性能問(wèn)題。Java中提供了很多原子操作類(lèi)來(lái)保證共享變量操作的原子性。這些原子操作的底層原理都是使用了CAS機(jī)制。既然用鎖或 synchronized 關(guān)鍵字可以實(shí)現(xiàn)原子操作,那么為什么還要用 CAS 呢,因?yàn)榧渔i或使用 synchronized 關(guān)鍵字帶來(lái)的性能損耗較大,而用 CAS 可以實(shí)現(xiàn)樂(lè)觀鎖,它實(shí)際上是直接利用了 CPU 層面的指令,沒(méi)有加鎖和線程上下文切換的開(kāi)銷(xiāo),所以性能很高。

一、CAS機(jī)制簡(jiǎn)介

1.1、悲觀鎖和樂(lè)觀鎖更新數(shù)據(jù)方式

CAS機(jī)制是一種數(shù)據(jù)更新的方式。在具體講什么是CAS機(jī)制之前,我們先來(lái)聊下在多線程環(huán)境下,對(duì)共享變量進(jìn)行數(shù)據(jù)更新的兩種模式:悲觀鎖模式和樂(lè)觀鎖模式。

悲觀鎖更新的方式認(rèn)為:在更新數(shù)據(jù)的時(shí)候大概率會(huì)有其他線程去爭(zhēng)奪共享資源,所以悲觀鎖的做法是:第一個(gè)獲取資源的線程會(huì)將資源鎖定起來(lái),其他沒(méi)爭(zhēng)奪到資源的線程只能進(jìn)入阻塞隊(duì)列,等第一個(gè)獲取資源的線程釋放鎖之后,這些線程才能有機(jī)會(huì)重新?tīng)?zhēng)奪資源。synchronized就是Java中悲觀鎖的典型實(shí)現(xiàn),synchronized使用起來(lái)非常簡(jiǎn)單方便,但是會(huì)使沒(méi)爭(zhēng)搶到資源的線程進(jìn)入阻塞狀態(tài),線程在阻塞狀態(tài)和Runnable狀態(tài)之間切換效率較低(比較慢)。比如你的更新操作其實(shí)是非??斓模@種情況下你還用synchronized將其他線程都鎖住了,線程從Blocked狀態(tài)切換回Runnable華的時(shí)間可能比你的更新操作的時(shí)間還要長(zhǎng)。

樂(lè)觀鎖更新方式認(rèn)為:在更新數(shù)據(jù)的時(shí)候其他線程爭(zhēng)搶這個(gè)共享變量的概率非常小,所以更新數(shù)據(jù)的時(shí)候不會(huì)對(duì)共享數(shù)據(jù)加鎖。但是在正式更新數(shù)據(jù)之前會(huì)檢查數(shù)據(jù)是否被其他線程改變過(guò),如果未被其他線程改變過(guò)就將共享變量更新成最新值,如果發(fā)現(xiàn)共享變量已經(jīng)被其他線程更新過(guò)了,就重試,直到成功為止。CAS機(jī)制就是樂(lè)觀鎖的典型實(shí)現(xiàn)。

1.2、什么是CAS機(jī)制

CAS,是Compare and Swap的簡(jiǎn)稱(chēng),是一種用于在多線程環(huán)境下實(shí)現(xiàn)同步功能的機(jī)制。CAS 操作包含三個(gè)操作數(shù) -- 內(nèi)存位置、預(yù)期數(shù)值和新值。CAS 的實(shí)現(xiàn)邏輯是將內(nèi)存位置處的數(shù)值與預(yù)期數(shù)值相比較,若相等,則將內(nèi)存位置處的值替換為新值。若不相等,則不做任何操作。

在 Java 中,Java 并沒(méi)有直接實(shí)現(xiàn) CAS,CAS 相關(guān)的實(shí)現(xiàn)是通過(guò) C++ 內(nèi)聯(lián)匯編的形式實(shí)現(xiàn)的。Java 代碼需通過(guò) JNI 才能調(diào)用。

CAS這個(gè)機(jī)制中有三個(gè)核心的參數(shù):

主內(nèi)存中存放的共享變量的值:V(一般情況下這個(gè)V是內(nèi)存的地址值,通過(guò)這個(gè)地址可以獲得內(nèi)存中的值)

工作內(nèi)存中共享變量的副本值,也叫預(yù)期值:A

需要將共享變量更新到的最新值:B

如上圖中,主存中保存V值,線程中要使用V值要先從主存中讀取V值到線程的工作內(nèi)存A中,然后計(jì)算后變成B值,最后再把B值寫(xiě)回到內(nèi)存V值中。多個(gè)線程共用V值都是如此操作。CAS的核心是在將B值寫(xiě)入到V之前要比較A值和V值是否相同,如果不相同證明此時(shí)V值已經(jīng)被其他線程改變,重新將V值賦給A,并重新計(jì)算得到B,如果相同,則將B值賦給V。

值得注意的是CAS機(jī)制中的這步步驟是原子性的(從指令層面提供的原子操作),所以CAS機(jī)制可以解決多線程并發(fā)編程對(duì)共享變量讀寫(xiě)的原子性問(wèn)題。

1.3、CAS與sychronized比較

從思想上來(lái)說(shuō):

①. synchronized屬于【悲觀鎖】

悲觀鎖認(rèn)為:程序中的【并發(fā)】情況嚴(yán)重,所以【嚴(yán)防死守】

②. CAS屬于【樂(lè)觀鎖】

樂(lè)觀鎖認(rèn)為:程序中的【并發(fā)】情況不那么嚴(yán)重,所以讓【線程不斷去嘗試更新】

這2種機(jī)制沒(méi)有絕對(duì)的好與壞,關(guān)鍵看使用場(chǎng)景。在并發(fā)量非常高的情況下,反而用同步鎖更合適一些。

1.4、Java中都有哪些地方應(yīng)用到了CAS機(jī)制呢?

a、Atomic系列類(lèi)

b、Lock系列類(lèi)底層實(shí)現(xiàn)

c、Java1.6以上版本,synchronized轉(zhuǎn)變?yōu)橹亓考?jí)鎖之前,也會(huì)采用CAS機(jī)制

1.5、CAS 實(shí)現(xiàn)自旋鎖

既然用鎖或 synchronized 關(guān)鍵字可以實(shí)現(xiàn)原子操作,那么為什么還要用 CAS 呢,因?yàn)榧渔i或使用 synchronized 關(guān)鍵字帶來(lái)的性能損耗較大,而用 CAS 可以實(shí)現(xiàn)樂(lè)觀鎖,它實(shí)際上是直接利用了 CPU 層面的指令,沒(méi)有加鎖和線程上下文切換的開(kāi)銷(xiāo),所以性能很高。

上面也說(shuō)了,CAS 是實(shí)現(xiàn)自旋鎖的基礎(chǔ),CAS 利用 CPU 指令保證了操作的原子性,以達(dá)到鎖的效果,至于自旋呢,看字面意思也很明白,自己旋轉(zhuǎn),翻譯成人話就是循環(huán),一般是用一個(gè)無(wú)限循環(huán)實(shí)現(xiàn)。這樣一來(lái),一個(gè)無(wú)限循環(huán)中,執(zhí)行一個(gè) CAS 操作,當(dāng)操作成功,返回 true 時(shí),循環(huán)結(jié)束;當(dāng)返回 false 時(shí),接著執(zhí)行循環(huán),繼續(xù)嘗試 CAS 操作,直到返回 true。

其實(shí) JDK 中有好多地方用到了 CAS ,尤其是 java.util.concurrent包下,比如 CountDownLatch、Semaphore、ReentrantLock 中,再比如 java.util.concurrent.atomic 包下,相信大家都用到過(guò) Atomic* ,比如 AtomicBoolean、AtomicInteger 等。

1.6、CAS機(jī)制優(yōu)缺點(diǎn)

CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問(wèn)題。ABA問(wèn)題,循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大和只能保證一個(gè)共享變量的原子操作

CAS機(jī)制CAS機(jī)制缺點(diǎn)

1>ABA問(wèn)題

ABA問(wèn)題:CAS在操作的時(shí)候會(huì)檢查變量的值是否被更改過(guò),如果沒(méi)有則更新值,但是帶來(lái)一個(gè)問(wèn)題,最開(kāi)始的值是A,接著變成B,最后又變成了A。經(jīng)過(guò)檢查這個(gè)值確實(shí)沒(méi)有修改過(guò),因?yàn)樽詈蟮闹颠€是A,但是實(shí)際上這個(gè)值確實(shí)已經(jīng)被修改過(guò)了。為了解決這個(gè)問(wèn)題,在每次進(jìn)行操作的時(shí)候加上一個(gè)版本號(hào),每次操作的就是兩個(gè)值,一個(gè)版本號(hào)和某個(gè)值,A——>B——>A問(wèn)題就變成了1A——>2B——>3A。在jdk中提供了AtomicStampedReference類(lèi)解決ABA問(wèn)題,用Pair這個(gè)內(nèi)部類(lèi)實(shí)現(xiàn),包含兩個(gè)屬性,分別代表版本號(hào)和引用,在compareAndSet中先對(duì)當(dāng)前引用進(jìn)行檢查,再對(duì)版本號(hào)標(biāo)志進(jìn)行檢查,只有全部相等才更新值。

AtomicStampedReferenceAtomicMarkableReference就是用來(lái)解決CAS中的ABA問(wèn)題的。他們解決ABA問(wèn)題的原理類(lèi)似,都是通過(guò)一個(gè)版本號(hào)來(lái)區(qū)分有沒(méi)被更新過(guò)。

AtomicStampedReference:帶版本戳的原子引用類(lèi)型,版本戳為int類(lèi)型。

AtomicMarkableReference:帶版本戳的原子引用類(lèi)型,版本戳為boolean類(lèi)型。

2>可能會(huì)消耗較高的CPU

看起來(lái)CAS比鎖的效率高,從阻塞機(jī)制變成了非阻塞機(jī)制,減少了線程之間等待的時(shí)間。每個(gè)方法不能絕對(duì)的比另一個(gè)好,在線程之間競(jìng)爭(zhēng)程度大的時(shí)候,如果使用CAS,每次都有很多的線程在競(jìng)爭(zhēng),也就是說(shuō)CAS機(jī)制不能更新成功。這種情況下CAS機(jī)制會(huì)一直重試,這樣就會(huì)比較耗費(fèi)CPU。因此可以看出,如果線程之間競(jìng)爭(zhēng)程度小,使用CAS是一個(gè)很好的選擇;但是如果競(jìng)爭(zhēng)很大,使用鎖可能是個(gè)更好的選擇。在并發(fā)量非常高的環(huán)境中,如果仍然想通過(guò)原子類(lèi)來(lái)更新的話,可以使用AtomicLong的替代類(lèi):LongAdder。

3>不能保證代碼塊的原子性

Java中的CAS機(jī)制只能保證一個(gè)共享變量的原子操作,而不能保證整個(gè)代碼塊的原子性。比如需要保證3個(gè)變量共同進(jìn)行原子性的更新,就不得不使用Synchronized了。

CAS機(jī)制優(yōu)點(diǎn)

可以保證變量操作的原子性;

并發(fā)量不是很高的情況下,使用CAS機(jī)制比使用鎖機(jī)制效率更高;

在線程對(duì)共享資源占用時(shí)間較短的情況下,使用CAS機(jī)制效率也會(huì)較高。

二、Java提供的CAS操作類(lèi)--Unsafe類(lèi)

2.1、Unsafe類(lèi)簡(jiǎn)介

在研究JDK中AQS時(shí),會(huì)發(fā)現(xiàn)這個(gè)類(lèi)很多地方都使用了CAS操作,在并發(fā)實(shí)現(xiàn)中CAS操作必須具備原子性,而且是硬件級(jí)別的原子性,Java被隔離在硬件之上,明顯力不從心,這時(shí)為了能直接操作操作系統(tǒng)層面,肯定要通過(guò)用C++編寫(xiě)的native本地方法來(lái)擴(kuò)展實(shí)現(xiàn)。JDK提供了一個(gè)類(lèi)來(lái)滿足CAS的要求,sun.misc.Unsafe,從名字上可以大概知道它用于執(zhí)行低級(jí)別、不安全的操作,AQS就是使用此類(lèi)完成硬件級(jí)別的原子操作。UnSafe通過(guò)JNI調(diào)用本地C++代碼,C++代碼調(diào)用CPU硬件指令集。

Unsafe是一個(gè)很強(qiáng)大的類(lèi),它可以分配內(nèi)存、釋放內(nèi)存、可以定位對(duì)象某字段的位置、可以修改對(duì)象的字段值、可以使線程掛起、使線程恢復(fù)、可進(jìn)行硬件級(jí)別原子的CAS操作等等。

從Java5開(kāi)始引入了對(duì)CAS機(jī)制的底層的支持,在這之前需要開(kāi)發(fā)人員編寫(xiě)相關(guān)的代碼才可以實(shí)現(xiàn)CAS。在原子變量類(lèi)Atomic中(例如AtomicInteger、AtomicLong)可以看到CAS操作的代碼,在這里的代碼都是調(diào)用了底層(核心代碼調(diào)用native修飾的方法)的實(shí)現(xiàn)方法。

在AtomicInteger源碼中可以看getAndSet方法和compareAndSet方法之間的關(guān)系,compareAndSet方法調(diào)用了底層的實(shí)現(xiàn),該方法可以實(shí)現(xiàn)與一個(gè)volatile變量的讀取和寫(xiě)入相同的效果。在前面說(shuō)到了volatile不支持例如i++這樣的復(fù)合操作,在Atomic中提供了實(shí)現(xiàn)該操作的方法。JVM對(duì)CAS的支持通過(guò)這些原子類(lèi)(Atomic***)暴露出來(lái),供我們使用。

而Atomic系類(lèi)的類(lèi)底層調(diào)用的是Unsafe類(lèi)的API,Unsafe類(lèi)提供了一系列的compareAndSwap*方法,下面就簡(jiǎn)單介紹下Unsafe類(lèi)的API:

long objectFieldOffset(Field field)方法:返回指定的變量在所屬類(lèi)中的內(nèi)存偏移地址,該偏移地址僅僅在該Unsafe函數(shù)中訪問(wèn)指定字段時(shí)使用。如下代碼使用Unsafe類(lèi)獲取變量value在AtomicLong對(duì)象中的內(nèi)存偏移。

static {
   try {
       valueOffset = unsafe.objectFieldOffset
           (AtomicInteger.class.getDeclaredField("value"));
   } catch (Exception ex) { throw new Error(ex); }
}
  • int arrayBaseOffset(Class arrayClass)方法:獲取數(shù)組中第一個(gè)元素的地址。
  • int arrayIndexScale(Class arrayClass)方法:獲取數(shù)組中一個(gè)元素占用的字節(jié)。
  • boolean compareAndSwapLong(Object obj, long offset, long expect, long update)方法:比較對(duì)象obj中偏移量為offset的變量的值是否與expect相等,相等則使用update值更新,然后返回true,否則返回false,這次處理器提供的一個(gè)原子性指令。
  • public native long getLongvolatile(Object obj, long offset)方法:獲取對(duì)象obj中偏移量為offset的變量對(duì)應(yīng)volatile語(yǔ)義的值。
  • void putLongvolatile(Object obj, long offset, long value)方法:設(shè)置obj對(duì)象中offset偏移的類(lèi)型為long的field的值為value,支持volatile語(yǔ)義。
  • void putOrderedLong(Object obj, long offset, long value)方法:設(shè)置obj對(duì)象中offset偏移地址對(duì)應(yīng)的long型field的值為value。這是一個(gè)有延遲的putLongvolatile方法,并且不保證值修改對(duì)其他線程立刻可見(jiàn)。只有在變量使用volatile修飾并且預(yù)計(jì)會(huì)被意外修改時(shí)才使用該方法。
  • void park(boolean isAbsolute, long time)方法:阻塞當(dāng)前線程,其中參數(shù)isAbsolute等于false且time等于0表示一直阻塞。time大于0表示等待指定的time后阻塞線程會(huì)被喚醒,這個(gè)time是個(gè)相對(duì)值,是個(gè)增量值,也就是相對(duì)當(dāng)前時(shí)間累加time后當(dāng)前線程就會(huì)被喚醒。如果isAbsolute等于true,并且time大于0,則表示阻塞的線程到指定的時(shí)間點(diǎn)后會(huì)被喚醒,這里time是個(gè)絕對(duì)時(shí)間,是將某個(gè)時(shí)間點(diǎn)換算為ms后的值。另外,當(dāng)其他線程調(diào)用了當(dāng)前阻塞線程的interrupt方法而中斷了當(dāng)前線程時(shí),當(dāng)前線程也會(huì)返回,而當(dāng)其他線程調(diào)用了unPark方法并且把當(dāng)前線程作為參數(shù)時(shí)當(dāng)前線程也會(huì)返回。
  • void unpark(Object thread)方法:?jiǎn)拘颜{(diào)用park后阻塞的線程。

下面是JDK8新增的函數(shù),這里只列出Long類(lèi)型操作。

long getAndSetLong(Object obj, long offset, long update)方法:獲取對(duì)象obj中偏移量為offset的變量volatile語(yǔ)義的當(dāng)前值,并設(shè)置變量volatile語(yǔ)義的值為update。

//這個(gè)方法只是封裝了compareAndSwapLong的使用,不需要自己寫(xiě)重試機(jī)制
public final long getAndSetLong(Object var1, long var2, long var4) {
    long var6;
    do {
        var6 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var6, var4));
    return var6;
}

long getAndAddLong(Object obj, long offset, long addValue)方法:獲取對(duì)象obj中偏移量為offset的變量volatile語(yǔ)義的當(dāng)前值,并設(shè)置變量值為原始值+addValue,原理和上面的方法類(lèi)似。

2.2、Unsafe類(lèi)的使用

三、CAS使用場(chǎng)景

  • 使用一個(gè)變量統(tǒng)計(jì)網(wǎng)站的訪問(wèn)量;
  • Atomic類(lèi)操作;
  • 數(shù)據(jù)庫(kù)樂(lè)觀鎖更新。

3.1、使用一個(gè)變量統(tǒng)計(jì)網(wǎng)站的訪問(wèn)量

要實(shí)現(xiàn)一個(gè)網(wǎng)站訪問(wèn)量的計(jì)數(shù)器,可以通過(guò)一個(gè)Long類(lèi)型的對(duì)象,并加上synchronized內(nèi)置鎖的方式。但是這種方式使得多線程的訪問(wèn)變成了串行的,同一時(shí)刻只能有一個(gè)線程可以更改long的值,那么為了能夠使多線程并發(fā)的更新long的值,我們可以使用J.U.C包中的Atomic原子類(lèi)。這些類(lèi)的更新是原子的,不需要加鎖即可實(shí)現(xiàn)并發(fā)的更新,并且是線程安全的。

可是Atomic原子類(lèi)是怎么保證并發(fā)更新的線程安全的呢?讓我們看一下AtomicLong的自增方法incrementAndGet():

public final long incrementAndGet() {
    // 無(wú)限循環(huán),即自旋
    for (;;) {
        // 獲取主內(nèi)存中的最新值
        long current = get();
        long next = current + 1;
        // 通過(guò)CAS原子更新,若能成功則返回,否則繼續(xù)自旋
        if (compareAndSet(current, next))
            return next;
    }
}
private volatile long value;
public final long get() {
    return value;
}

可以發(fā)現(xiàn)其內(nèi)部保持著一個(gè)volatile修飾的long變量,volatile保證了long的值更新后,其他線程能立即獲得最新的值。
在incrementAndGet中首先是一個(gè)無(wú)限循環(huán)(自旋),然后獲取long的最新值,將long加1,然后通過(guò)compareAndSet()方法嘗試將long的值有current更新為next。如果能更新成功,則說(shuō)明當(dāng)前還沒(méi)有其他線程更新該值,則返回next,如果更新失敗,則說(shuō)明有其他線程提前更新了該值,則當(dāng)前線程繼續(xù)自旋嘗試更新。

簡(jiǎn)單總結(jié)

總體來(lái)說(shuō),AtomicBoolean、AtomicInteger、AtomicLong和AtomicReference原理比較簡(jiǎn)單:使用CAS保證原子性,使用volatile保證可見(jiàn)性,最終能保證共享變量操作的線程安全。

AtomicLongArray、AtomicIntArray和AtomicReferenceArray的實(shí)現(xiàn)原理略有不同,是用CAS機(jī)制配合final機(jī)制來(lái)實(shí)現(xiàn)共享變量操作的線程安全的。感興趣的同學(xué)可以自己分析下,也是比較簡(jiǎn)單的。

CAS的操作其底層是通過(guò)調(diào)用sun.misc.Unsafe類(lèi)中的CompareAndSwap的方法保證線程安全的。Unsafe類(lèi)中主要有下面三種CompareAndSwap方法:

public final native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update);
public final native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
public final native boolean compareAndSwapLong(Object obj, long offset, long expect, long update);

可以看到這些方法都是native的,需要調(diào)用JNI接口,也即通過(guò)操作系統(tǒng)來(lái)保證這些方法的執(zhí)行。

3.2、現(xiàn)在我們嘗試在代碼中引入AtomicInteger類(lèi)

在使用Integer的時(shí)候,必須加上synchronized保證不會(huì)出現(xiàn)并發(fā)線程同時(shí)訪問(wèn)的情況

public class AtomicInteger {
    private static Integer count =0;
    public static void main(String[] args) {
        //開(kāi)啟兩個(gè)線程
        for(int i=0;i<2;i++)
        {
            new Thread(new Runnable()
            {
                @Override
                public void run()
                {
                    //每個(gè)線程當(dāng)中讓count自增1000次
                    for(int j=0;j<1000;j++)
                    {
                        increment();
                    }
                }
            }).start();
        }
        //讓主線程睡2秒,避免直接打印count值為0
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("count="+count);
    }
    //加上synchronized保證不會(huì)出現(xiàn)并發(fā)線程同時(shí)訪問(wèn)的情況,否則結(jié)果可能只有1千多
     public synchronized static void increment() {
        count++;
    }
}

而在AtomicInteger中卻不用加上synchronized,在這里AtomicInteger是提供原子操作的

在某些情況下,原子類(lèi)代碼的性能會(huì)比Synchronized更好,因?yàn)闆](méi)有加鎖的線程同步上下文切換開(kāi)銷(xiāo),底層采用了CAS機(jī)制保證共享變量原子性,還配合volatile保證內(nèi)存可見(jiàn)性,最終能保證共享變量操作的線程安全。

四、Java中的原子操作類(lèi)

在JDK1.5版本之前,多行代碼的原子性主要通過(guò)synchronized關(guān)鍵字進(jìn)行保證。在JDK1.5版本,Java提供了原子類(lèi)型專(zhuān)門(mén)確保變量操作的原子性。所謂的原子操作類(lèi),指的是java.util.concurrent.atomic包下,一系列以Atomic開(kāi)頭的包裝類(lèi)。例如:AtomicBoolean,AtomicInteger,AtomicLong。它們分別用于Boolean,Integer,Long類(lèi)型的原子性操作。

為了方面對(duì)這些類(lèi)逐級(jí)掌握,我將這些原子類(lèi)型分為以下幾類(lèi):

  • 普通原子類(lèi)型:提供對(duì)boolean、int、long和對(duì)象的原子性操作。
    • AtomicBoolean
    • AtomicInteger
    • AtomicLong
    • AtomicReference
  • 原子類(lèi)型數(shù)組:提供對(duì)數(shù)組元素的原子性操作。
    • AtomicLongArray
    • AtomicIntegerArray
    • AtomicReferenceArray
  • 原子類(lèi)型字段更新器:提供對(duì)指定對(duì)象的指定字段進(jìn)行原子性操作。
    • AtomicLongFieldUpdater
    • AtomicIntegerFieldUpdater
    • AtomicReferenceFieldUpdater
  • 帶版本號(hào)的原子引用類(lèi)型:以版本戳的方式解決原子類(lèi)型的ABA問(wèn)題。
    • AtomicStampedReference
    • AtomicMarkableReference
  • 原子累加器(JDK1.8):AtomicLong和AtomicDouble的升級(jí)類(lèi)型,專(zhuān)門(mén)用于數(shù)據(jù)統(tǒng)計(jì),性能更高。
    • DoubleAccumulator
    • DoubleAdder
    • LongAccumulator
    • LongAdder

原子類(lèi)型累加器是JDK1.8引進(jìn)的并發(fā)新技術(shù),它可以看做AtomicLong和AtomicDouble的部分加強(qiáng)類(lèi)型。低并發(fā)、一般的業(yè)務(wù)場(chǎng)景下AtomicLong是足夠了。如果并發(fā)量很多,存在大量寫(xiě)多讀少的情況,那LongAdder可能更合適,代價(jià)是消耗更多的內(nèi)存空間。

AtomicLong中有個(gè)內(nèi)部變量value保存著實(shí)際的long值,所有的操作都是針對(duì)該變量進(jìn)行。也就是說(shuō),高并發(fā)環(huán)境下,value變量其實(shí)是一個(gè)熱點(diǎn),也就是N個(gè)線程競(jìng)爭(zhēng)一個(gè)熱點(diǎn)。在并發(fā)量較低的環(huán)境下,線程沖突的概率比較小,自旋的次數(shù)不會(huì)很多。但是,高并發(fā)環(huán)境下,N個(gè)線程同時(shí)進(jìn)行自旋操作,會(huì)出現(xiàn)大量失敗并不斷自旋的情況,此時(shí)AtomicLong的自旋會(huì)成為瓶頸。

這就是LongAdder引入的初衷——解決高并發(fā)環(huán)境下AtomicLong的自旋瓶頸問(wèn)題。

LongAdder的基本思路就是分散熱點(diǎn),將value值分散到一個(gè)數(shù)組中,不同線程會(huì)命中到數(shù)組的不同槽中,各個(gè)線程只對(duì)自己槽中的那個(gè)值進(jìn)行CAS操作,這樣熱點(diǎn)就被分散了,沖突的概率就小很多。如果要獲取真正的long值,只要將各個(gè)槽中的變量值累加返回。這種做法有沒(méi)有似曾相識(shí)的感覺(jué)?沒(méi)錯(cuò),
ConcurrentHashMap中的“分段鎖”其實(shí)就是類(lèi)似的思路。

參考鏈接:

并發(fā)編程的基石——CAS機(jī)制

深入理解CAS機(jī)制

Atomic系列類(lèi)整體介紹

原子類(lèi)型累加載器

總結(jié)

本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!

相關(guān)文章

  • Springboot項(xiàng)目中如何讓非Spring管理的類(lèi)獲得一個(gè)注入的Bean

    Springboot項(xiàng)目中如何讓非Spring管理的類(lèi)獲得一個(gè)注入的Bean

    這篇文章主要介紹了Springboot項(xiàng)目中如何讓非Spring管理的類(lèi)獲得一個(gè)注入的Bean問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • 關(guān)于Spring啟動(dòng)流程及Bean生命周期梳理

    關(guān)于Spring啟動(dòng)流程及Bean生命周期梳理

    這篇文章主要介紹了關(guān)于Spring啟動(dòng)流程及Bean生命周期梳理,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • Java SpringSecurity+JWT實(shí)現(xiàn)登錄認(rèn)證

    Java SpringSecurity+JWT實(shí)現(xiàn)登錄認(rèn)證

    這篇文章主要介紹了Java SpringSecurity+JWT實(shí)現(xiàn)登錄認(rèn)證,首先通過(guò)給需要登錄認(rèn)證的模塊添加mall-security依賴(lài)展開(kāi)介紹,感興趣的朋友可以參考一下
    2022-06-06
  • idea 離線安裝lombok插件的方法步驟(圖文)

    idea 離線安裝lombok插件的方法步驟(圖文)

    這篇文章主要介紹了idea 離線安裝lombok插件的方法步驟(圖文),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-01-01
  • SpringBoot使用過(guò)濾器、攔截器和監(jiān)聽(tīng)器的案例代碼(Springboot搭建java項(xiàng)目)

    SpringBoot使用過(guò)濾器、攔截器和監(jiān)聽(tīng)器的案例代碼(Springboot搭建java項(xiàng)目)

    這篇文章主要介紹了SpringBoot使用過(guò)濾器、攔截器和監(jiān)聽(tīng)器(Springboot搭建java項(xiàng)目),本文是基于Springboot搭建java項(xiàng)目,結(jié)合案例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-02-02
  • Spring請(qǐng)求路徑帶參數(shù)URL使用注解的寫(xiě)法說(shuō)明

    Spring請(qǐng)求路徑帶參數(shù)URL使用注解的寫(xiě)法說(shuō)明

    這篇文章主要介紹了Spring請(qǐng)求路徑帶參數(shù)URL使用注解的寫(xiě)法說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • java 對(duì)稱(chēng)二叉樹(shù)的判斷

    java 對(duì)稱(chēng)二叉樹(shù)的判斷

    這篇文章主要介紹了java 對(duì)稱(chēng)二叉樹(shù)的判斷,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-02-02
  • Java內(nèi)存模型final的內(nèi)存語(yǔ)義

    Java內(nèi)存模型final的內(nèi)存語(yǔ)義

    這篇文章主要介紹了Java內(nèi)存模型final的內(nèi)存語(yǔ)義,上篇介紹volatile的內(nèi)存語(yǔ)義,本文講述的是final的內(nèi)存語(yǔ)義,相比之下,final域的讀和寫(xiě)更像是普通變量的訪問(wèn)。下面我們一起來(lái)看看文章學(xué)校內(nèi)容吧,需要的朋友可以參考一下
    2021-11-11
  • Mybatis插件+注解實(shí)現(xiàn)數(shù)據(jù)脫敏方式

    Mybatis插件+注解實(shí)現(xiàn)數(shù)據(jù)脫敏方式

    這篇文章主要介紹了Mybatis插件+注解實(shí)現(xiàn)數(shù)據(jù)脫敏方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • 詳解Spring Boot 自定義PropertySourceLoader

    詳解Spring Boot 自定義PropertySourceLoader

    這篇文章主要介紹了詳解Spring Boot 自定義PropertySourceLoader,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-05-05

最新評(píng)論