Java多線程之并發(fā)編程的基石CAS機(jī)制詳解
前言: synchronized保證了線程安全,但是在某些情況下,卻不是一個(gè)最優(yōu)選擇,關(guān)鍵在于性能問(wèn)題。Java中提供了很多原子操作類來(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)銷,所以性能很高。
一、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)稱,是一種用于在多線程環(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系列類
b、Lock系列類底層實(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)銷,所以性能很高。
上面也說(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)銷大和只能保證一個(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類解決ABA問(wèn)題,用Pair這個(gè)內(nèi)部類實(shí)現(xiàn),包含兩個(gè)屬性,分別代表版本號(hào)和引用,在compareAndSet中先對(duì)當(dāng)前引用進(jìn)行檢查,再對(duì)版本號(hào)標(biāo)志進(jìn)行檢查,只有全部相等才更新值。
AtomicStampedReference和AtomicMarkableReference就是用來(lái)解決CAS中的ABA問(wèn)題的。他們解決ABA問(wèn)題的原理類似,都是通過(guò)一個(gè)版本號(hào)來(lái)區(qū)分有沒(méi)被更新過(guò)。
AtomicStampedReference:帶版本戳的原子引用類型,版本戳為int類型。
AtomicMarkableReference:帶版本戳的原子引用類型,版本戳為boolean類型。
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)更新的話,可以使用AtomicLong的替代類: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操作類--Unsafe類
2.1、Unsafe類簡(jiǎn)介
在研究JDK中AQS時(shí),會(huì)發(fā)現(xiàn)這個(gè)類很多地方都使用了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)滿足CAS的要求,sun.misc.Unsafe,從名字上可以大概知道它用于執(zhí)行低級(jí)別、不安全的操作,AQS就是使用此類完成硬件級(jí)別的原子操作。UnSafe通過(guò)JNI調(diào)用本地C++代碼,C++代碼調(diào)用CPU硬件指令集。
Unsafe是一個(gè)很強(qiáng)大的類,它可以分配內(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。在原子變量類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ò)這些原子類(Atomic***)暴露出來(lái),供我們使用。
而Atomic系類的類底層調(diào)用的是Unsafe類的API,Unsafe類提供了一系列的compareAndSwap*方法,下面就簡(jiǎn)單介紹下Unsafe類的API:
long objectFieldOffset(Field field)方法:返回指定的變量在所屬類中的內(nèi)存偏移地址,該偏移地址僅僅在該Unsafe函數(shù)中訪問(wèn)指定字段時(shí)使用。如下代碼使用Unsafe類獲取變量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偏移的類型為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類型操作。
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,原理和上面的方法類似。
2.2、Unsafe類的使用
三、CAS使用場(chǎng)景
- 使用一個(gè)變量統(tǒng)計(jì)網(wǎng)站的訪問(wèn)量;
- Atomic類操作;
- 數(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類型的對(duì)象,并加上synchronized內(nèi)置鎖的方式。但是這種方式使得多線程的訪問(wèn)變成了串行的,同一時(shí)刻只能有一個(gè)線程可以更改long的值,那么為了能夠使多線程并發(fā)的更新long的值,我們可以使用J.U.C包中的Atomic原子類。這些類的更新是原子的,不需要加鎖即可實(shí)現(xiàn)并發(fā)的更新,并且是線程安全的。
可是Atomic原子類是怎么保證并發(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類中的CompareAndSwap的方法保證線程安全的。Unsafe類中主要有下面三種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類
在使用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是提供原子操作的
在某些情況下,原子類代碼的性能會(huì)比Synchronized更好,因?yàn)闆](méi)有加鎖的線程同步上下文切換開(kāi)銷,底層采用了CAS機(jī)制保證共享變量原子性,還配合volatile保證內(nèi)存可見(jiàn)性,最終能保證共享變量操作的線程安全。
四、Java中的原子操作類
在JDK1.5版本之前,多行代碼的原子性主要通過(guò)synchronized關(guān)鍵字進(jìn)行保證。在JDK1.5版本,Java提供了原子類型專門確保變量操作的原子性。所謂的原子操作類,指的是java.util.concurrent.atomic包下,一系列以Atomic開(kāi)頭的包裝類。例如:AtomicBoolean,AtomicInteger,AtomicLong。它們分別用于Boolean,Integer,Long類型的原子性操作。

為了方面對(duì)這些類逐級(jí)掌握,我將這些原子類型分為以下幾類:
- 普通原子類型:提供對(duì)boolean、int、long和對(duì)象的原子性操作。
- AtomicBoolean
- AtomicInteger
- AtomicLong
- AtomicReference
- 原子類型數(shù)組:提供對(duì)數(shù)組元素的原子性操作。
- AtomicLongArray
- AtomicIntegerArray
- AtomicReferenceArray
- 原子類型字段更新器:提供對(duì)指定對(duì)象的指定字段進(jìn)行原子性操作。
- AtomicLongFieldUpdater
- AtomicIntegerFieldUpdater
- AtomicReferenceFieldUpdater
- 帶版本號(hào)的原子引用類型:以版本戳的方式解決原子類型的ABA問(wèn)題。
- AtomicStampedReference
- AtomicMarkableReference
- 原子累加器(JDK1.8):AtomicLong和AtomicDouble的升級(jí)類型,專門用于數(shù)據(jù)統(tǒng)計(jì),性能更高。
- DoubleAccumulator
- DoubleAdder
- LongAccumulator
- LongAdder
原子類型累加器是JDK1.8引進(jìn)的并發(fā)新技術(shù),它可以看做AtomicLong和AtomicDouble的部分加強(qiáng)類型。低并發(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í)就是類似的思路。
參考鏈接:
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
Spring?Boot數(shù)據(jù)響應(yīng)問(wèn)題實(shí)例詳解
這篇文章主要給大家介紹了關(guān)于Spring?Boot數(shù)據(jù)響應(yīng)問(wèn)題的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-03-03
使用Java實(shí)現(xiàn)查找并移除字符串中的Emoji
Emoji 實(shí)際上是 UTF-8 (Unicode) 字符集上的特殊字符,這篇文章主要介紹了如何使用Java實(shí)現(xiàn)查找并移除字符串中的Emoji,感興趣的可以了解下2024-03-03
Java實(shí)現(xiàn)十進(jìn)制與二進(jìn)制互轉(zhuǎn)的示例詳解
這篇文章主要為大家詳細(xì)介紹了Java如何實(shí)現(xiàn)十進(jìn)制與二進(jìn)制的互轉(zhuǎn),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定幫助,需要的可以參考一下2022-11-11
使用JPA中@Query 注解實(shí)現(xiàn)update 操作方法(必看)
下面小編就為大家?guī)?lái)一篇使用JPA中@Query 注解實(shí)現(xiàn)update 操作方法(必看)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06
springboot啟動(dòng)加載CommandLineRunner @PostConstruct問(wèn)題
這篇文章主要介紹了springboot啟動(dòng)加載CommandLineRunner @PostConstruct問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-08-08
java 通過(guò)發(fā)送json,post請(qǐng)求,返回json數(shù)據(jù)的方法
下面小編就為大家分享一篇java 通過(guò)發(fā)送json,post請(qǐng)求,返回json數(shù)據(jù)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
java多線程并發(fā)中使用Lockers類將多線程共享資源鎖定
Lockers在多線程編程里面一個(gè)重要的概念是鎖定,如果一個(gè)資源是多個(gè)線程共享的,為了保證數(shù)據(jù)的完整性,在進(jìn)行事務(wù)性操作時(shí)需要將共享資源鎖定,這樣可以保證在做事務(wù)性操作時(shí)只有一個(gè)線程能對(duì)資源進(jìn)行操作,下面看一個(gè)示例2014-01-01
Java ConcurrentHashMap鎖分段機(jī)制使用及代碼實(shí)例
ConcurrentHashMap是Java中的一種線程安全的哈希表,通過(guò)鎖分段機(jī)制提高了并發(fā)性能,在Java 8中,ConcurrentHashMap引入了CAS操作和更復(fù)雜的節(jié)點(diǎn)繼承結(jié)構(gòu),進(jìn)一步優(yōu)化了并發(fā)操作2025-01-01

