1. 悲觀鎖與樂(lè)觀鎖

我們都知道,cpu是時(shí)分復(fù)用的,也就是把cpu的時(shí)間片,分配給不同的thread/process輪流執(zhí)行,時(shí)間片與時(shí)間片之間,需要進(jìn)行cpu切換,也就是會(huì)發(fā)生進(jìn)程的切換。切換涉及到清空寄存器,緩存數(shù)據(jù)。然后重新加載新的thread所需數(shù)據(jù)。當(dāng)一個(gè)線程被掛起時(shí),加入到阻塞隊(duì)列,在一定的時(shí)間或條件下,在通過(guò)notify(),notifyAll()喚醒回來(lái)。在某個(gè)資源不可用的時(shí)候,就將cpu讓出,把當(dāng)前等待線程切換為阻塞狀態(tài)。等到資源(比如一個(gè)共享數(shù)據(jù))可用了,那么就將線程喚醒,讓他進(jìn)入runnable狀態(tài)等待cpu調(diào)度。這就是典型的悲觀鎖的實(shí)現(xiàn)。獨(dú)占鎖是一種悲觀鎖,synchronized就是一種獨(dú)占鎖,它假設(shè)最壞的情況,并且只有在確保其它線程不會(huì)造成干擾的情況下執(zhí)行,會(huì)導(dǎo)致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。

但是,由于在進(jìn)程掛起和恢復(fù)執(zhí)行過(guò)程中存在著很大的開(kāi)銷(xiāo)。當(dāng)一個(gè)線程正在等待鎖時(shí),它不能做任何事,所以悲觀鎖有很大的缺點(diǎn)。舉個(gè)例子,如果一個(gè)線程需要某個(gè)資源,但是這個(gè)資源的占用時(shí)間很短,當(dāng)線程第一次搶占這個(gè)資源時(shí),可能這個(gè)資源被占用,如果此時(shí)掛起這個(gè)線程,可能立刻就發(fā)現(xiàn)資源可用,然后又需要花費(fèi)很長(zhǎng)的時(shí)間重新?lián)屨兼i,時(shí)間代價(jià)就會(huì)非常的高。

所以就有了樂(lè)觀鎖的概念,他的核心思路就是,每次不加鎖而是假設(shè)沒(méi)有沖突而去完成某項(xiàng)操作,如果因?yàn)闆_突失敗就重試,直到成功為止。在上面的例子中,某個(gè)線程可以不讓出cpu,而是一直while循環(huán),如果失敗就重試,直到成功為止。所以,當(dāng)數(shù)據(jù)爭(zhēng)用不嚴(yán)重時(shí),樂(lè)觀鎖效果更好。比如CAS就是一種樂(lè)觀鎖思想的應(yīng)用。

2.   java中CAS的實(shí)現(xiàn)

CAS就是Compare and Swap的意思,比較并操作。很多的cpu直接支持CAS指令。CAS是項(xiàng)樂(lè)觀鎖技術(shù),當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,而其它線程都失敗,失敗的線程并不會(huì)被掛起,而是被告知這次競(jìng)爭(zhēng)中失敗,并可以再次嘗試。CAS有3個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則什么都不做。

JDK1.5中引入了底層的支持,在int、long和對(duì)象的引用等類型上都公開(kāi)了CAS的操作,并且JVM把它們編譯為底層硬件提供的最有效的方法,在運(yùn)行CAS的平臺(tái)上,運(yùn)行時(shí)把它們編譯為相應(yīng)的機(jī)器指令。在Java.util.concurrent.atomic包下面的所有的原子變量類型中,比如AtomicInteger,都使用了這些底層的JVM支持為數(shù)字類型的引用類型提供一種高效的CAS操作。

在CAS操作中,會(huì)出現(xiàn)ABA問(wèn)題。就是如果V的值先由A變成B,再由B變成A,那么仍然認(rèn)為是發(fā)生了變化,并需要重新執(zhí)行算法中的步驟。有簡(jiǎn)單的解決方案:不是更新某個(gè)引用的值,而是更新兩個(gè)值,包括一個(gè)引用和一個(gè)版本號(hào),即使這個(gè)值由A變?yōu)锽,然后為變?yōu)锳,版本號(hào)也是不同的。AtomicStampedReference和AtomicMarkableReference支持在兩個(gè)變量上執(zhí)行原子的條件更新。AtomicStampedReference更新一個(gè)“對(duì)象-引用”二元組,通過(guò)在引用上加上“版本號(hào)”,從而避免ABA問(wèn)題,AtomicMarkableReference將更新一個(gè)“對(duì)象引用-布爾值”的二元組。

3.  AtomicInteger的實(shí)現(xiàn)。

AtomicInteger 是一個(gè)支持原子操作的 Integer 類,就是保證對(duì)AtomicInteger類型變量的增加和減少操作是原子性的,不會(huì)出現(xiàn)多個(gè)線程下的數(shù)據(jù)不一致問(wèn)題。如果不使用 AtomicInteger,要實(shí)現(xiàn)一個(gè)按順序獲取的 ID,就必須在每次獲取時(shí)進(jìn)行加鎖操作,以避免出現(xiàn)并發(fā)時(shí)獲取到同樣的 ID 的現(xiàn)象。

接下來(lái)通過(guò)源代碼來(lái)看AtomicInteger具體是如何實(shí)現(xiàn)的原子操作。

首先看incrementAndGet() 方法,下面是具體的代碼。

photoshop培訓(xùn),電腦培訓(xùn),電腦維修培訓(xùn),移動(dòng)軟件開(kāi)發(fā)培訓(xùn),網(wǎng)站設(shè)計(jì)培訓(xùn),網(wǎng)站建設(shè)培訓(xùn)

public final int incrementAndGet() { 
 for (;;) { 
 int current = get(); 
 int next = current + 1; 
 if (compareAndSet(current, next)) 
 return next; 
 }&nb