Java中CAS機(jī)制實(shí)現(xiàn)方法詳解
概述
傳統(tǒng)的并發(fā)控制手段,如使用synchronized
關(guān)鍵字或者ReentrantLock
等互斥鎖機(jī)制,雖然能夠有效防止資源的競(jìng)爭(zhēng)沖突,但也可能帶來(lái)額外的性能開(kāi)銷(xiāo),如上下文切換、鎖競(jìng)爭(zhēng)導(dǎo)致的線程阻塞等。而此時(shí)就出現(xiàn)了一種樂(lè)觀鎖的策略,以其非阻塞、輕量級(jí)的特點(diǎn),在某些場(chǎng)合下能更好地提升并發(fā)性能,其中最為關(guān)鍵的技術(shù)便是Compare And Swap(簡(jiǎn)稱(chēng)CAS)。
CAS是一種無(wú)鎖算法,它在硬件級(jí)別提供了原子性的條件更新操作,允許線程在不加鎖的情況下實(shí)現(xiàn)對(duì)共享變量的修改。在Java中,CAS機(jī)制被廣泛應(yīng)用于java.util.concurrent.atomic
包下的原子類(lèi)以及高級(jí)并發(fā)工具類(lèi)如AbstractQueuedSynchronizer
(AQS)的實(shí)現(xiàn)中。
CAS的基本概念
CAS是原子指令,一種基于鎖的操作,而且是樂(lè)觀鎖,又稱(chēng)無(wú)鎖機(jī)制。CAS操作包含三個(gè)基本操作數(shù):內(nèi)存位置、期望值和新值。
- 主內(nèi)存中存放的共享變量的值:V(一般情況下這個(gè)V是內(nèi)存的地址值,通過(guò)這個(gè)地址可以獲得內(nèi)存中的值)。
- 工作內(nèi)存中共享變量的副本值,也叫預(yù)期值:A。
- 需要將共享變量更新到的最新值:B。
CAS基本原理
在執(zhí)行CAS操作時(shí),計(jì)算機(jī)會(huì)檢查內(nèi)存位置當(dāng)前是否存放著期望值,如果是,則將內(nèi)存位置的值更新為新值;若不是,則不做任何修改,保持原有值不變,并返回當(dāng)前內(nèi)存位置的實(shí)際值。
CAS操作通過(guò)一條CPU的原子指令,保證了比較和更新的原子性。在執(zhí)行CAS操作時(shí),CPU會(huì)判斷當(dāng)前系統(tǒng)是否為多核系統(tǒng),如果是,則會(huì)給總線加鎖,確保只有一個(gè)線程能夠執(zhí)行CAS操作。這種獨(dú)占式的原子性實(shí)現(xiàn)方式,比起使用synchronized
等重量級(jí)鎖,具有更短的排他時(shí)間,因此在多線程情況下性能更佳。
Java中的CAS實(shí)現(xiàn)
在Java中,CAS機(jī)制被封裝在jdk.internal.misc.Unsafe
類(lèi)中,盡管這個(gè)類(lèi)并不建議在普通應(yīng)用程序中直接使用,但它是構(gòu)建更高層次并發(fā)工具的基礎(chǔ),例如java.util.concurrent.atomic
包下的原子類(lèi)如AtomicInteger
、AtomicLong
等。這些原子類(lèi)通過(guò)JNI調(diào)用底層硬件提供的CAS
指令,從而在Java層面上實(shí)現(xiàn)了無(wú)鎖并發(fā)操作。
Java的標(biāo)準(zhǔn)庫(kù)中,特別是jdk.internal.misc.Unsafe
類(lèi)提供了一系列compareAndSwapXXX
方法,這些方法底層確實(shí)是通過(guò)C++編寫(xiě)的內(nèi)聯(lián)匯編來(lái)調(diào)用對(duì)應(yīng)CPU架構(gòu)的cmpxchg
指令,從而實(shí)現(xiàn)原子性的比較和交換操作。
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; //由這里可以看出來(lái),依賴(lài)jdk.internal.misc.Unsafe實(shí)現(xiàn)的 private static final jdk.internal.misc.Unsafe U = jdk.internal.misc.Unsafe.getUnsafe(); private static final long VALUE = U.objectFieldOffset(AtomicInteger.class, "value"); private volatile int value; public final boolean compareAndSet(int expectedValue, int newValue) { // 調(diào)用 jdk.internal.misc.Unsafe的compareAndSetInt方法 return U.compareAndSetInt(this, VALUE, expectedValue, newValue); } }
Unsafe
中的compareAndSetInt
使用了@HotSpotIntrinsicCandidate
注解修飾,@HotSpotIntrinsicCandidate
注解是Java HotSpot虛擬機(jī)(JVM)的一個(gè)特性注解,它表明標(biāo)注的方法有可能會(huì)被HotSpot JVM識(shí)別為“內(nèi)聯(lián)候選”,當(dāng)JVM發(fā)現(xiàn)有方法被標(biāo)記為內(nèi)聯(lián)候選時(shí),會(huì)嘗試?yán)玫讓佑布峁┑脑又噶睿ū热?code>cmpxchg指令)直接替換掉原本的Java方法調(diào)用,從而在運(yùn)行時(shí)獲得更好的性能。
public final class Unsafe { @HotSpotIntrinsicCandidate public final native boolean compareAndSetInt(Object o, long offset, int expected, int x); }
compareAndSetInt
這個(gè)方法我們可以從openjdk
的hotspot
源碼(位置:hotspot/src/share/vm/prims/unsafe.cpp
)中可以找到。hostspot
中的Unsafe_CompareAndSetInt
函數(shù)會(huì)統(tǒng)一調(diào)用Atomic
的cmpxchg
函數(shù)UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSetInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x)) { oop p = JNIHandles::resolve(obj); jint* addr = (jint *)index_oop_from_field_offset_long(p, offset); // 統(tǒng)一調(diào)用Atomic的cmpxchg函數(shù) return (jint)(Atomic::cmpxchg(x, addr, e)) == e; } UNSAFE_END
而
Atomic
的cmpxchg
函數(shù)源碼(位置:hotspot/src/share/vm/runtime/atomic.hpp
)如下:/** *這是按字節(jié)大小進(jìn)行的`cmpxchg`操作的默認(rèn)實(shí)現(xiàn)。它使用按整數(shù)大小進(jìn)行的`cmpxchg`來(lái)模擬按字節(jié)大小進(jìn)行的`cmpxchg`。不同的平臺(tái)可以通過(guò)定義自己的內(nèi)聯(lián)定義以及定義`VM_HAS_SPECIALIZED_CMPXCHG_BYTE`來(lái)覆蓋這個(gè)默認(rèn)實(shí)現(xiàn)。這將導(dǎo)致使用特定于平臺(tái)的實(shí)現(xiàn)而不是默認(rèn)實(shí)現(xiàn)。 * exchange_value:要交換的新值。 * dest:指向目標(biāo)字節(jié)的指針。 * compare_value:要比較的值。 * order:內(nèi)存順序。 */ inline jbyte Atomic::cmpxchg(jbyte exchange_value, volatile jbyte* dest, jbyte compare_value, cmpxchg_memory_order order) { STATIC_ASSERT(sizeof(jbyte) == 1); volatile jint* dest_int = static_cast<volatile jint*>(align_ptr_down(dest, sizeof(jint))); size_t offset = pointer_delta(dest, dest_int, 1); // 獲取當(dāng)前整數(shù)大小的值,并將其轉(zhuǎn)換為字節(jié)數(shù)組。 jint cur = *dest_int; jbyte* cur_as_bytes = reinterpret_cast<jbyte*>(&cur); // 設(shè)置當(dāng)前整數(shù)中對(duì)應(yīng)字節(jié)的值為compare_value。這確保了如果初始的整數(shù)值不是我們要找的值,那么第一次的cmpxchg操作會(huì)失敗。 cur_as_bytes[offset] = compare_value; // 在循環(huán)中,不斷嘗試更新目標(biāo)字節(jié)的值。 do { // new_val jint new_value = cur; // 復(fù)制當(dāng)前整數(shù)值,并設(shè)置其中對(duì)應(yīng)字節(jié)的值為exchange_value。 reinterpret_cast<jbyte*>(&new_value)[offset] = exchange_value; // 嘗試使用新的整數(shù)值替換目標(biāo)整數(shù)。 jint res = cmpxchg(new_value, dest_int, cur, order); if (res == cur) break; // 如果返回值與原始整數(shù)值相同,說(shuō)明操作成功。 // 更新當(dāng)前整數(shù)值為cmpxchg操作的結(jié)果。 cur = res; // 如果目標(biāo)字節(jié)的值仍然是我們之前設(shè)置的值,那么繼續(xù)循環(huán)并再次嘗試。 } while (cur_as_bytes[offset] == compare_value); // 返回更新后的字節(jié)值 return cur_as_bytes[offset]; }
cmpxchg
指令是多數(shù)現(xiàn)代CPU支持的原子指令,它能在多線程環(huán)境下確保一次比較和交換操作的原子性,有效解決了多線程環(huán)境下數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題,避免了數(shù)據(jù)不一致的情況。例如,在更新一個(gè)共享變量時(shí),如果期望值與當(dāng)前值相匹配,則原子性地更新為新值,否則不進(jìn)行更新操作,這樣就能在無(wú)鎖的情況下實(shí)現(xiàn)對(duì)共享資源的安全訪問(wèn)。
我們以java.util.concurrent.atomic
包下的AtomicInteger
為例,分析其compareAndSet
方法。
而由cmpxchg
函數(shù)中的do...while
我們也可以看出,當(dāng)多個(gè)線程同時(shí)嘗試更新同一內(nèi)存位置,且它們的期望值相同但只有一個(gè)線程能夠成功更新時(shí),其他線程的CAS
操作會(huì)失敗。對(duì)于失敗的線程,常見(jiàn)的做法是采用自旋鎖的形式,即循環(huán)重試直到成功為止。這種方式在低競(jìng)爭(zhēng)或短時(shí)間窗口內(nèi)的并發(fā)更新時(shí),相比于傳統(tǒng)的鎖機(jī)制,它避免了線程的阻塞和喚醒帶來(lái)的開(kāi)銷(xiāo),所以它的性能會(huì)更優(yōu)。
什么是unsafe
什么是unsafe呢?Java語(yǔ)言不像C,C++那樣可以直接訪問(wèn)底層操作系統(tǒng),但是JVM為我們提供了一個(gè)后門(mén),這個(gè)后門(mén)就是unsafe。unsafe為我們提供了硬件級(jí)別的原子操作。
CAS是一種原子操作。那么Java是怎樣來(lái)使用CAS的呢?
我們知道,在Java中,如果一個(gè)方法是native的,那Java就不負(fù)責(zé)具體實(shí)現(xiàn)它,而是交給底層的JVM使用c或者c++去實(shí)現(xiàn)。
Unsafe
類(lèi)是JDK提供的一個(gè)不安全的類(lèi),它提供了一些底層的操作,包括內(nèi)存操作、線程調(diào)度、對(duì)象實(shí)例化等。它的作用是讓Java可以在底層直接操作內(nèi)存,從而提高程序的效率。但是,由于Unsafe類(lèi)是不安全的,所以只有JDK開(kāi)發(fā)人員才能使用它,普通開(kāi)發(fā)者不建議使用。它里面大多是一些native方法,其中就有幾個(gè)關(guān)于CAS的:
boolean compareAndSwapObject(Object o, long offset,Object expected, Object x); boolean compareAndSwapInt(Object o, long offset,int expected,int x); boolean compareAndSwapLong(Object o, long offset,long expected,long x);
調(diào)用
compareAndSwapInt、compareAndSwapLong或compareAndSwapObject
方法時(shí),會(huì)傳入三個(gè)參數(shù),分別是需要修改的變量V、期望的值A(chǔ)和新值B。方法會(huì)先讀取變量V的當(dāng)前值,如果當(dāng)前值等于期望的值A(chǔ),則使用新值B來(lái)更新變量V,否則不做任何操作。
方法會(huì)返回更新操作是否成功的標(biāo)志,如果更新成功,則返回true,否則返回false。
由于CAS操作是基于底層硬件支持的原子性指令來(lái)實(shí)現(xiàn)的,所以它可以保證操作的原子性和線程安全性,同時(shí)也可以避免使用鎖帶來(lái)的性能開(kāi)銷(xiāo)。因此,CAS操作廣泛應(yīng)用于并發(fā)編程中,比如實(shí)現(xiàn)無(wú)鎖數(shù)據(jù)結(jié)構(gòu)、實(shí)現(xiàn)線程安全的計(jì)數(shù)器等。
原子操作類(lèi)解析
看一下AtomicInteger
當(dāng)中常用的自增方法incrementAndGet
public class AtomicInteger extends Number implements java.io.Serializable { private static final long serialVersionUID = 6214790243416807050L; // setup to use Unsafe.compareAndSwapInt for updates private static final Unsafe unsafe = Unsafe.getUnsafe(); 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; /** * Atomically increments by one the current value. * * @return the updated value */ public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; } } public final class Unsafe { public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; } }
這段代碼是一個(gè)無(wú)限循環(huán),也就是CAS的自旋(底層為do-while循環(huán)),循環(huán)體中做了三件事:
- 獲取當(dāng)前值
- 當(dāng)前值 + 1,計(jì)算出目標(biāo)值
- 進(jìn)行
CAS
操作,如果成功則跳出循環(huán),如果失敗則重復(fù)上述步驟
這里需要注意的重點(diǎn)是get方法,這個(gè)方法的作用是獲取變量的當(dāng)前值。
volatile
關(guān)鍵字來(lái)保證(保證線程間的可見(jiàn)性)。
compareAndSet
方法的實(shí)現(xiàn)很簡(jiǎn)單,只有一行代碼。這里涉及到兩個(gè)重要的對(duì)象,一個(gè)是unsafe,一個(gè)是valueOffset。 unsafe上面提到,就不用多說(shuō)了,對(duì)于valueOffset
對(duì)象,是通過(guò)unsafe.objectFiledOffset
方法得到,所代表的是AtomicInteger
對(duì)象value成員變量在內(nèi)存中的偏移量。我們可以簡(jiǎn)單的把valueOffset理解為value變量的內(nèi)存地址。
我們上面說(shuō)過(guò),CAS機(jī)制中使用了3個(gè)基本操作數(shù):內(nèi)存地址V,舊的預(yù)期值A(chǔ),要修改的新值B。
而unsafe的compareAndSwapInt
方法的參數(shù)包括了這三個(gè)基本元素:valueOffset參數(shù)代表了V,expect參數(shù)代表了A,update參數(shù)代表了B。
正是unsafe的compareAndSwapInt
方法保證了Compare和Swap操作之間的原子性操作。
CAS機(jī)制的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
一開(kāi)始在文中我們?cè)?jīng)提到過(guò),CAS
是一種樂(lè)觀鎖,而且是一種非阻塞的輕量級(jí)的樂(lè)觀鎖,什么是非阻塞式的呢?其實(shí)就是一個(gè)線程想要獲得鎖,對(duì)方會(huì)給一個(gè)回應(yīng)表示這個(gè)鎖能不能獲得。在資源競(jìng)爭(zhēng)不激烈的情況下性能高,相比synchronized
重量鎖,synchronized
會(huì)進(jìn)行比較復(fù)雜的加鎖,解鎖和喚醒操作。
缺點(diǎn)
CPU開(kāi)銷(xiāo)過(guò)大:CAS摒棄了傳統(tǒng)的鎖機(jī)制,避免了因獲取和釋放鎖產(chǎn)生的上下文切換和線程阻塞,從而顯著提升了系統(tǒng)的并發(fā)性能。并且由于CAS操作是基于硬件層面的原子性保證,所以它不會(huì)出現(xiàn)死鎖問(wèn)題,這對(duì)于復(fù)雜并發(fā)場(chǎng)景下的程序設(shè)計(jì)特別重要。另外,CAS策略下線程在無(wú)法成功更新變量時(shí)不需要掛起和喚醒,只需通過(guò)簡(jiǎn)單的循環(huán)重試即可。
但是,在高并發(fā)條件下,頻繁的CAS操作可能導(dǎo)致大量的自旋重試,消耗大量的CPU資源。尤其是在競(jìng)爭(zhēng)激烈的場(chǎng)景中,線程可能花費(fèi)大量的時(shí)間在不斷地嘗試更新變量,而不是做有用的工作。這個(gè)由剛才
cmpxchg
函數(shù)可以看出。對(duì)于這個(gè)問(wèn)題,我們可以參考synchronize
中輕量級(jí)鎖經(jīng)過(guò)自旋,超過(guò)一定閾值后升級(jí)為重量級(jí)鎖的原理,我們也可以給自旋設(shè)置一個(gè)次數(shù),如果超過(guò)這個(gè)次數(shù),就把線程掛起或者執(zhí)行失敗。(自適應(yīng)自旋)。另外,Java中的原子類(lèi)也提供了解決辦法,比如
LongAdder
以及DoubleAdder
等,LongAdder
過(guò)分散競(jìng)爭(zhēng)點(diǎn)來(lái)減少自旋鎖的沖突。它并沒(méi)有像AtomicLong那樣維護(hù)一個(gè)單一的共享變量,而是維護(hù)了一個(gè)Base
值和一組Cell
(桶)結(jié)構(gòu)。每個(gè)Cell
本質(zhì)上也是一個(gè)可以進(jìn)行原子操作的計(jì)數(shù)器,多個(gè)線程可以分別在一個(gè)獨(dú)立的Cell
上進(jìn)行累加,只有在必要時(shí)才將各個(gè)Cell
的值匯總到Base
中。這樣一來(lái),大部分時(shí)候線程間的修改不再是集中在同一個(gè)變量上,從而降低了競(jìng)爭(zhēng)強(qiáng)度,提高了并發(fā)性能。ABA問(wèn)題: 單純的
CAS
無(wú)法識(shí)別一個(gè)值被多次修改后又恢復(fù)原值的情況,可能導(dǎo)致錯(cuò)誤的判斷。在高并發(fā)場(chǎng)景下,使用CAS
操作可能存在ABA
問(wèn)題,也就是在一個(gè)值被修改之前,先被其他線程修改為另外的值,然后再被修改回原值,此時(shí)CAS操作會(huì)認(rèn)為這個(gè)值沒(méi)有被修改過(guò),導(dǎo)致數(shù)據(jù)不一致。為了解決ABA問(wèn)題,Java中提供了
AtomicStampedReference類(lèi)
(原子標(biāo)記參考),該類(lèi)通過(guò)使用版本號(hào)的方式來(lái)解決ABA問(wèn)題。每個(gè)共享變量都會(huì)關(guān)聯(lián)一個(gè)版本號(hào),CAS操作時(shí)需要同時(shí)檢查值和版本號(hào)是否匹配。因此,如果共享變量的值被改變了,版本號(hào)也會(huì)發(fā)生變化,即使共享變量被改回原來(lái)的值,版本號(hào)也不同,因此CAS操作會(huì)失敗。在java中鎖分為樂(lè)觀鎖和悲觀鎖。悲觀鎖是將資源鎖住,等一個(gè)之前獲得鎖的線程釋放鎖之后,下一個(gè)線程才可以訪問(wèn)。而樂(lè)觀鎖采取了一種寬泛的態(tài)度,通過(guò)某種方式不加鎖來(lái)處理資源,比如通過(guò)給記錄加version來(lái)獲取數(shù)據(jù),性能較悲觀鎖有很大的提高。
不能保證代碼塊的原子性: CAS機(jī)制所保證的知識(shí)一個(gè)變量的原子性操作,而不能保證整個(gè)代碼塊的原子性。比如需要保證3個(gè)變量共同進(jìn)行原子性的更新,就不得不使用synchronized了。
Java的原子類(lèi)就提供了類(lèi)似的實(shí)現(xiàn),如AtomicStampedReference
和AtomicMarkableReference
引入了附加的標(biāo)記位或版本號(hào),以便區(qū)分不同的修改序列。
CAS應(yīng)用場(chǎng)景
主要在并發(fā)編程的應(yīng)用中非常的廣泛,通常用于實(shí)現(xiàn)樂(lè)觀鎖和無(wú)鎖算法
- 線程安全計(jì)數(shù)器:由于CAS操作是原子性的,因此CAS可以用來(lái)實(shí)現(xiàn)一個(gè)線程安全的計(jì)數(shù)器;
- 隊(duì)列: 在并發(fā)編程中,隊(duì)列經(jīng)常用于多線程之間的數(shù)據(jù)交換。使用CAS可以實(shí)現(xiàn)無(wú)鎖的非阻塞隊(duì)列(Lock-Free Queue);
- 數(shù)據(jù)庫(kù)并發(fā)控制: 樂(lè)觀鎖就是通過(guò)CAS實(shí)現(xiàn)的,它可以在數(shù)據(jù)庫(kù)并發(fā)控制中保證多個(gè)事務(wù)同時(shí)訪問(wèn)同一數(shù)據(jù)時(shí)的一致性;
- 自旋鎖: 自旋鎖是一種非阻塞鎖,當(dāng)線程嘗試獲取鎖時(shí),如果鎖已經(jīng)被其他線程占用,則線程不會(huì)進(jìn)入休眠,而是一直在自旋等待鎖的釋放。自旋鎖的實(shí)現(xiàn)可以使用CAS操作;
- 線程池: 在多線程編程中,線程池可以提高線程的使用效率。使用CAS操作可以避免對(duì)線程池的加鎖,從而提高線程池的并發(fā)性能。
CAS機(jī)制優(yōu)化
雖然CAS機(jī)制具有很多優(yōu)點(diǎn),但在實(shí)際應(yīng)用中也存在一些問(wèn)題,如自旋等待導(dǎo)致的CPU資源浪費(fèi)等。為了優(yōu)化CAS機(jī)制的性能,可以采取以下措施:
- 自適應(yīng)自旋:根據(jù)歷史經(jīng)驗(yàn)動(dòng)態(tài)調(diào)整自旋次數(shù),避免過(guò)多的自旋等待。
- 批量操作:將多個(gè)CAS操作組合成一個(gè)原子塊,減少CAS操作的次數(shù)。
- 減少鎖競(jìng)爭(zhēng):通過(guò)合理的數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)和線程調(diào)度策略,減少CAS操作的競(jìng)爭(zhēng),提高并發(fā)性能。
總結(jié)
Java
中的CAS
原理及其在并發(fā)編程中的應(yīng)用是一項(xiàng)非常重要的技術(shù)。CAS
利用CPU
硬件提供的原子指令,實(shí)現(xiàn)了在無(wú)鎖環(huán)境下的高效并發(fā)控制,避免了傳統(tǒng)鎖機(jī)制帶來(lái)的上下文切換和線程阻塞開(kāi)銷(xiāo)。Java通過(guò)JNI
接口調(diào)用底層的CAS
指令,封裝在jdk.internal.misc
類(lèi)和java.util.concurrent.atomic
包下的原子類(lèi)中,為我們提供了簡(jiǎn)潔易用的API來(lái)實(shí)現(xiàn)無(wú)鎖編程。
CAS
在帶來(lái)并發(fā)性能提升的同時(shí),也可能引發(fā)循環(huán)開(kāi)銷(xiāo)過(guò)大、ABA
問(wèn)題等問(wèn)題。針對(duì)這些問(wèn)題,Java
提供了如LongAdder
、AtomicStampedReference
和AtomicMarkableReference
等工具類(lèi)來(lái)解決ABA
問(wèn)題,同時(shí)也通過(guò)自適應(yīng)自旋、適時(shí)放棄自旋轉(zhuǎn)而進(jìn)入阻塞等待等方式降低循環(huán)開(kāi)銷(xiāo)。
理解和熟練掌握CAS
原理及其在Java
中的應(yīng)用,有助于我們?cè)陂_(kāi)發(fā)高性能并發(fā)程序時(shí)作出更明智的選擇,既能提高系統(tǒng)并發(fā)性能,又能保證數(shù)據(jù)的正確性和一致性。
到此這篇關(guān)于Java中CAS機(jī)制實(shí)現(xiàn)方法的文章就介紹到這了,更多相關(guān)Java CAS機(jī)制詳解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot動(dòng)態(tài)定時(shí)功能實(shí)現(xiàn)方案詳解
在SpringBoot項(xiàng)目中簡(jiǎn)單使用定時(shí)任務(wù),不過(guò)由于要借助cron表達(dá)式且都提前定義好放在配置文件里,不能在項(xiàng)目運(yùn)行中動(dòng)態(tài)修改任務(wù)執(zhí)行時(shí)間,實(shí)在不太靈活?,F(xiàn)在我們就來(lái)實(shí)現(xiàn)可以動(dòng)態(tài)修改cron表達(dá)式的定時(shí)任務(wù),感興趣的可以了解一下2022-11-11DolphinScheduler容錯(cuò)Master源碼分析
這篇文章主要為大家介紹了DolphinScheduler容錯(cuò)Master源碼分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02Java實(shí)現(xiàn)動(dòng)態(tài)日歷效果
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)動(dòng)態(tài)日歷效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07Java中將String類(lèi)型依照某個(gè)字符分割成數(shù)組的方法
下面小編就為大家分享一篇Java中將String類(lèi)型依照某個(gè)字符分割成數(shù)組的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-03-03Java爬蟲(chóng)實(shí)現(xiàn)Jsoup利用dom方法遍歷Document對(duì)象
本文主要介紹了Java爬蟲(chóng)實(shí)現(xiàn)Jsoup利用dom方法遍歷Document對(duì)象,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05