1、Java Concurrent API 中的 Lock 接口(Lock interface)是什么?對(duì)比同步它有什么優(yōu)勢(shì)?
答:Lock
接口比同步方法和同步塊提供了更具擴(kuò)展性的鎖操作。他們?cè)试S更靈活的結(jié)構(gòu),可以具有完全不同的性質(zhì),并且可以支持多個(gè)相關(guān)類的條件對(duì)象。
優(yōu)勢(shì):
- 可以使鎖更公平;
- 可以使線程在等待鎖的時(shí)候響應(yīng)中斷;
- 可以讓線程嘗試獲取鎖,并在無(wú)法獲取鎖的時(shí)候立即返回或者等待一段時(shí)間;
- 可以在不同的范圍,以不同的順序獲取和釋放鎖;
整體上來(lái)說(shuō)
Lock
是synchronized
的擴(kuò)展版,Lock
提供了無(wú)條件的、可輪詢的(tryLock()
方法)、定時(shí)的(tryLock()
帶參方法)、可中斷的(lockInterruptibly()
)、可多條件隊(duì)列的(newCondition()
方法)鎖操作。另外Lock 的實(shí)現(xiàn)類基本都支持非公平鎖(默認(rèn))和公平鎖,synchronized
只支持非公平鎖,當(dāng)然,在大部分情況下,非公平鎖是高效的選擇。
2、什么是CAS?
-
CAS
是compare and swap
的縮寫,即我們所說(shuō)的比較交換。 -
CAS
是一種基于鎖的操作,而且是樂(lè)觀鎖。在 Java 中鎖分為樂(lè)觀鎖和悲觀鎖。悲觀鎖是將資源鎖住,等一個(gè)之前獲得鎖的線程釋放鎖之后,下一個(gè)線程才可以訪問(wèn)。而樂(lè)觀鎖采取了一種寬泛的態(tài)度,通過(guò)某種方式不加鎖來(lái)處理資源,比如通過(guò)給記錄加version
來(lái)獲取數(shù)據(jù),性能較悲觀鎖有很大的提高。 -
CAS
操作包含三個(gè)操作數(shù) ——內(nèi)存位置(V
)、預(yù)期原值(A
)和新值(B
)。如果內(nèi)存地址里面的值和A
的值是一樣的,那么就將內(nèi)存里面的值更新成B
。CAS
是通過(guò)無(wú)限循環(huán)來(lái)獲取數(shù)據(jù)的,若果在第一輪循環(huán)中,a
線程獲取地址里面的值被b
線程修改了,那么a
線程需要自旋,到下次循環(huán)才有可能機(jī)會(huì)執(zhí)行。 -
java.util.concurrent.atomic
包下的類大多是使用CAS
操作來(lái)實(shí)現(xiàn)的(AtomicInteger,AtomicBoolean,AtomicLong)
。
3、樂(lè)觀鎖和悲觀鎖的理解及如何實(shí)現(xiàn),有哪些實(shí)現(xiàn)方式?
-
悲觀鎖: 總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫(kù)里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。再比如 Java 里面的同步原語(yǔ)
synchronized
關(guān)鍵字的實(shí)現(xiàn)也是悲觀鎖。 -
樂(lè)觀鎖: 顧名思義,就是很樂(lè)觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒(méi)有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)等機(jī)制。樂(lè)觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫(kù)提供的類似于
write_condition
機(jī)制,其實(shí)都是提供。樂(lè)觀鎖。在Java
中java.util.concurrent.atomic
包下面的原子變量類就是使用了樂(lè)觀鎖的一種實(shí)現(xiàn)方式CAS
實(shí)現(xiàn)的。
3.1、樂(lè)觀鎖的實(shí)現(xiàn)方式:
- 使用版本標(biāo)識(shí)來(lái)確定讀到的數(shù)據(jù)與提交時(shí)的數(shù)據(jù)是否一致。提交后修改版本標(biāo)識(shí),不一致時(shí)可以采取丟棄和再次嘗試的策略。
-
Java
中的Compare and Swap
即CAS
,當(dāng)多個(gè)線程嘗試使用CAS
同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,而其它線程都失敗,失敗的線程并不會(huì)被掛起,而是被告知這次競(jìng)爭(zhēng)中失敗,并可以再次嘗試。
3.2、CAS 操作中包含三個(gè)操作數(shù)
- 需要讀寫的內(nèi)存位置(
V
) - 進(jìn)行比較的預(yù)期原值(
A
) - 擬寫入的新值(
B
)
如果內(nèi)存位置
V
的值與預(yù)期原值A
相匹配,那么處理器會(huì)自動(dòng)將該位置值更新為新值B
。否則處理器不做任何操作。
4、CAS 的會(huì)產(chǎn)生什么問(wèn)題?
-
ABA
問(wèn)題: 比如說(shuō)一個(gè)線程one
從內(nèi)存位置V
中取出A
,這時(shí)候另一個(gè)線程two
也從內(nèi)存中取出A
,并且two
進(jìn)行了一些操作變成了B
,然后two
又將V
位置的數(shù)據(jù)變成A
,這時(shí)候線程one
進(jìn)行CAS
操作發(fā)現(xiàn)內(nèi)存中仍然是A
,然后one
操作成功。盡管線程one
的CAS
操作成功,但可能存在潛藏的問(wèn)題。從Java1.5
開(kāi)始JDK
的atomic
包里提供了一個(gè)類AtomicStampedReference
來(lái)解決ABA
問(wèn)題。 -
循環(huán)時(shí)間長(zhǎng)開(kāi)銷大: 對(duì)于資源競(jìng)爭(zhēng)嚴(yán)重(線程沖突嚴(yán)重)的情況,
CAS
自旋的概率會(huì)比較大,從而浪費(fèi)更多的CPU
資源,效率低于synchronized
。 -
只能保證一個(gè)共享變量的原子操作: 當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí),我們可以使用循環(huán)
CAS
的方式來(lái)保證原子操作,但是對(duì)多個(gè)共享變量操作時(shí),循環(huán)CAS
就無(wú)法保證操作的原子性,這個(gè)時(shí)候就可以用鎖。
5、AQS?
答:AQS
隊(duì)列同步器是用來(lái)構(gòu)建鎖或其他同步組件的基礎(chǔ)框架,它使用一個(gè) volatile int state
變量作為共享資源,如果線程獲取資源失敗,則進(jìn)入同步隊(duì)列等待;如果獲取成功就執(zhí)行臨界區(qū)代碼,釋放資源時(shí)會(huì)通知同步隊(duì)列中的等待線程。比如我們提到的ReentrantLock
,Semaphore
,其他的諸如ReentrantReadWriteLock
,SynchronousQueue
,FutureTask
等等皆是基于AQS
的。
同步器的主要使用方式是繼承,子類通過(guò)繼承同步器并實(shí)現(xiàn)它的抽象方法來(lái)管理同步狀態(tài),對(duì)同步狀態(tài)進(jìn)行更改需要使用同步器提供的 3個(gè)方法 getState()
、setState()
和 compareAndSetState()
,它們保證狀態(tài)改變是安全的。子類推薦被定義為自定義同步組件的靜態(tài)內(nèi)部類,同步器自身沒(méi)有實(shí)現(xiàn)任何同步接口,它僅僅定義若干同步狀態(tài)獲取和釋放的方法,同步器既支持獨(dú)占式也支持共享式。同步器是實(shí)現(xiàn)鎖的關(guān)鍵,在鎖的實(shí)現(xiàn)中聚合同步器,利用同步器實(shí)現(xiàn)鎖的語(yǔ)義。鎖面向使用者,定義了使用者與鎖交互的接口,隱藏實(shí)現(xiàn)細(xì)節(jié);同步器面向鎖的實(shí)現(xiàn)者,簡(jiǎn)化了鎖的實(shí)現(xiàn)方式,屏蔽了同步狀態(tài)管理、線程排隊(duì)、等待與喚醒等底層操作。
每當(dāng)有新線程請(qǐng)求資源時(shí)都會(huì)進(jìn)入一個(gè)等待隊(duì)列,只有當(dāng)持有鎖的線程釋放鎖資源后該線程才能持有資源。等待隊(duì)列通過(guò)雙向鏈表實(shí)現(xiàn),線程被封裝在鏈表的 Node
節(jié)點(diǎn)中,Node
的等待狀態(tài)包括:
-
CANCELLED
(線程已取消) -
SIGNAL
(線程需要喚醒) -
CONDITION
(線程正在等待) -
PROPAGATE
(后繼節(jié)點(diǎn)會(huì)傳播喚醒操作,只在共享模式下起作用)。
6、AQS的原理?
-
獲取同步狀態(tài): 調(diào)用
acquire()
方法,維護(hù)一個(gè)同步隊(duì)列,使用tryAcquire()
方法安全地獲取線程同步狀態(tài),獲取失敗的線程會(huì)被構(gòu)造同步節(jié)點(diǎn)并通過(guò)addWaiter()
方法加入到同步隊(duì)列的尾部,在隊(duì)列中自旋。之后調(diào)用acquireQueued()
方法使得該節(jié)點(diǎn)以死循環(huán)的方式獲取同步狀態(tài),如果獲取不到則阻塞,被阻塞線程的喚醒主要依靠前驅(qū)節(jié)點(diǎn)的出隊(duì)或被中斷實(shí)現(xiàn),移出隊(duì)列或停止自旋的條件是前驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)且成功獲取了同步狀態(tài)。 -
釋放同步狀態(tài): 同步器調(diào)用
tryRelease()
方法釋放同步狀態(tài),然后調(diào)用unparkSuccessor()
方法喚醒頭節(jié)點(diǎn)的后繼節(jié)點(diǎn),使后繼節(jié)點(diǎn)重新嘗試獲取同步狀態(tài)。
6.1、CLH隊(duì)列:
答: CLH(Craig,Landin,and Hagersten)
隊(duì)列是一個(gè)虛擬的雙向隊(duì)列(虛擬的雙向隊(duì)列即不存在隊(duì)列實(shí)例,僅存在結(jié)點(diǎn)之間的關(guān)聯(lián)關(guān)系)。AQS
是將每條請(qǐng)求共享資源的線程封裝成一個(gè)CLH
鎖隊(duì)列的一個(gè)結(jié)點(diǎn)(Node)
來(lái)實(shí)現(xiàn)鎖的分配。
6.2、AQS原理圖
答: 獲取同步狀態(tài)時(shí),調(diào)用 acquire()
方法,維護(hù)一個(gè)同步隊(duì)列,使用 tryAcquire()
方法安全地獲取線程同步狀態(tài),獲取失敗的線程會(huì)被構(gòu)造同步節(jié)點(diǎn)并通過(guò) addWaiter()
方法加入到同步隊(duì)列的尾部,在隊(duì)列中自旋。之后調(diào)用 acquireQueued()
方法使得該節(jié)點(diǎn)以死循環(huán)的方式獲取同步狀態(tài),如果獲取不到則阻塞,被阻塞線程的喚醒主要依靠前驅(qū)節(jié)點(diǎn)的出隊(duì)或被中斷實(shí)現(xiàn),移出隊(duì)列或停止自旋的條件是前驅(qū)節(jié)點(diǎn)
是頭結(jié)點(diǎn)且成功獲取了同步狀態(tài)。
釋放同步狀態(tài)時(shí),同步器調(diào)用 tryRelease()
方法釋放同步狀態(tài),然后調(diào)用 unparkSuccessor()
方法喚醒頭節(jié)點(diǎn)的后繼節(jié)點(diǎn),使后繼節(jié)點(diǎn)重新嘗試獲取同步狀態(tài)。
AQS
使用一個(gè)int
成員變量來(lái)表示同步狀態(tài),通過(guò)內(nèi)置的FIFO
隊(duì)列來(lái)完成獲取資源線程的排隊(duì)工作。AQS
使用CAS
對(duì)該同步狀態(tài)進(jìn)行原子操作實(shí)現(xiàn)對(duì)其值的修改。
private volatile int state;//共享變量,使用volatile修飾保證線程可見(jiàn)性
狀態(tài)信息通過(guò)protected
類型的getState()
,setState()
,compareAndSetState()
進(jìn)行操作:
//返回同步狀態(tài)的當(dāng)前值
protected final int getState() {
return state;
}
// 設(shè)置同步狀態(tài)的值
protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)將同步狀態(tài)值設(shè)置為給定值update如果當(dāng)前同步狀態(tài)的值等于expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
6.3、AQS對(duì)資源的共享方式
答:AQS
定義兩種資源共享方式,獨(dú)占模式通過(guò) acquire
和 release
方法獲取和釋放鎖,共享模式通過(guò)acquireShared
和 releaseShared
方法獲取和釋放鎖。
-
Exclusive
(獨(dú)占):只有一個(gè)線程能執(zhí)行,如ReentrantLock
。又可分為公平鎖和非公平鎖:- 公平鎖:按照線程在隊(duì)列中的排隊(duì)順序,先到者先拿到鎖;
- 非公平鎖:當(dāng)線程要獲取鎖時(shí),無(wú)視隊(duì)列順序直接去搶鎖,誰(shuí)搶到就是誰(shuí)的。
-
Share
(共享):多個(gè)線程可同時(shí)執(zhí)行,如Semaphore/CountDownLatch
。Semaphore
、CountDownLatch
、CyclicBarrier
、ReadWriteLock
我們都會(huì)在后面講到。
ReentrantReadWriteLock
可以看成是組合式,因?yàn)?code>ReentrantReadWriteLock也就是讀寫鎖允許多個(gè)線程同時(shí)對(duì)某一資源進(jìn)行讀。
不同的自定義同步器爭(zhēng)用共享資源的方式也不同。自定義同步器在實(shí)現(xiàn)時(shí)只需要實(shí)現(xiàn)共享資源
state
的獲取與釋放方式即可,至于具體線程等待隊(duì)列的維護(hù)(如獲取資源失敗入隊(duì)/喚醒出隊(duì)等),AQS
已經(jīng)在頂層實(shí)現(xiàn)好了。
6.4、AQS底層使用了模板方法模式
答: 同步器的設(shè)計(jì)是基于模板方法模式的,如果需要自定義同步器一般的方式是這樣(模板方法模式很經(jīng)典的一個(gè)應(yīng)用):
- 使用者繼承
AbstractQueuedSynchronizer
并重寫指定的方法。(這些重寫方法很簡(jiǎn)單,無(wú)非是對(duì)于共享資源state
的獲取和釋放) - 將
AQS
組合在自定義同步組件的實(shí)現(xiàn)中,并調(diào)用其模板方法,而這些模板方法會(huì)調(diào)用使用者重寫的方法。
6.5、AQS使用了模板方法模式,自定義同步器時(shí)需要重寫下面幾個(gè)AQS提供的模板方法。
//該線程是否正在獨(dú)占資源。只有用到condition才需要去實(shí)現(xiàn)它。
isHeldExclusively()
//獨(dú)占方式。嘗試獲取資源,成功則返回true,失敗則返回false。
tryAcquire(int)
//獨(dú)占方式。嘗試釋放資源,成功則返回true,失敗則返回false。
tryRelease(int)
//共享方式。嘗試獲取資源。負(fù)數(shù)表示失??;0表示成功,但沒(méi)有剩余可用資源;正數(shù)表示成功,且有剩余資源。
tryAcquireShared(int)
//共享方式。嘗試釋放資源,成功則返回true,失敗則返回false。
tryReleaseShared(int)
答: 默認(rèn)情況下,每個(gè)方法都拋出 UnsupportedOperationException
。 這些方法的實(shí)現(xiàn)必須是內(nèi)部線程安全的,并且通常應(yīng)該簡(jiǎn)短而不是阻塞。AQS
類中的其他方法都是final
,所以無(wú)法被其他類使用,只有這幾個(gè)方法可以被其他類使用:
- 以
ReentrantLock
為例,state
初始化為0,表示未鎖定狀態(tài)。A
線程lock()
時(shí),會(huì)調(diào)用tryAcquire()
獨(dú)占該鎖并將state
+1。此后,其他線程再tryAcquire()
時(shí)就會(huì)失敗,直到A
線程unlock()
到state=0
(即釋放鎖)為止,其它線程才有機(jī)會(huì)獲取該鎖。當(dāng)然,釋放鎖之前,A
線程自己是可以重復(fù)獲取此鎖的(state
會(huì)累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多么次,這樣才能保證state
是能回到零態(tài)的。 - 再以
CountDownLatch
以例,任務(wù)分為N
個(gè)子線程去執(zhí)行,state
也初始化為N
(注意N
要與線程個(gè)數(shù)一致)。這N
個(gè)子線程是并行執(zhí)行的,每個(gè)子線程執(zhí)行完后countDown()
一次,state
會(huì)CAS(Compare and Swap)
減1。等到所有子線程都執(zhí)行完后(即state=0
),會(huì)unpark()
調(diào)用主線程,然后主調(diào)用線程就會(huì)從await()
函數(shù)返回,繼續(xù)后面動(dòng)作。
一般來(lái)說(shuō),自定義同步器要么是獨(dú)占方法,要么是共享方式,他們也只需實(shí)現(xiàn)
tryAcquire-tryRelease
、tryAcquireShared-tryReleaseShared
中的一種即可。但AQS
也支持自定義同步器同時(shí)實(shí)現(xiàn)獨(dú)占和共享兩種方式,如ReentrantReadWriteLock
。
7、為什么只有前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)時(shí)才能嘗試獲取同步狀態(tài)?
答: 頭節(jié)點(diǎn)是成功獲取到同步狀態(tài)的節(jié)點(diǎn),后繼節(jié)點(diǎn)的線程被喚醒后需要檢查自己的前驅(qū)節(jié)點(diǎn)是否是頭節(jié)
點(diǎn)。
目的:維護(hù)同步隊(duì)列的 FIFO
原則,節(jié)點(diǎn)和節(jié)點(diǎn)在循環(huán)檢查的過(guò)程中基本不通信,而是簡(jiǎn)單判斷自己的前驅(qū)是否為頭節(jié)點(diǎn),這樣就使節(jié)點(diǎn)的釋放規(guī)則符合 FIFO
,并且也便于對(duì)過(guò)早通知的處理,過(guò)早通知指前驅(qū)節(jié)點(diǎn)不是頭節(jié)點(diǎn)的線程由于中斷被喚醒。
8、AQS共享式式獲取/釋放鎖的原理?
答:
-
同步狀態(tài): 調(diào)用
acquireShared()
方法,該方法調(diào)用tryAcquireShared()
方法嘗試獲取同步狀態(tài),返回值為int
類型,返回值不小于0 表示能獲取同步狀態(tài)。因此在共享式獲取鎖的自旋過(guò)程中,成功獲取同步狀態(tài)并退出自旋的條件就是該方法的返回值不小于0。 -
釋放同步狀態(tài): 調(diào)用
releaseShared()
方法,釋放后會(huì)喚醒后續(xù)處于等待狀態(tài)的節(jié)點(diǎn)。它和獨(dú)占式的區(qū)別在于tryReleaseShared()
方法必須確保同步狀態(tài)安全釋放,通過(guò)循環(huán) CAS 保證,因?yàn)獒尫磐綘顟B(tài)的操作會(huì)同時(shí)來(lái)自多個(gè)線程。
9、什么是可重入鎖(ReentrantLock)?
答:ReentrantLock
重入鎖,是實(shí)現(xiàn)Lock
接口的一個(gè)類,也是在實(shí)際編程中使用頻率很高的一個(gè)鎖,支持重入性,表示能夠?qū)蚕碣Y源能夠重復(fù)加鎖,即當(dāng)前線程獲取該鎖再次獲取不會(huì)被阻塞。
在Java關(guān)鍵字
synchronized
隱式支持重入性,synchronized
通過(guò)獲取自增,釋放自減的方式實(shí)現(xiàn)重入。與此同時(shí),ReentrantLock
還支持公平鎖和非公平鎖兩種方式。
重入性的實(shí)現(xiàn)原理
答: 要想支持重入性,就要解決兩個(gè)問(wèn)題:
-
在線程獲取鎖的時(shí)候,如果已經(jīng)獲取鎖的線程是當(dāng)前線程的話則直接再次獲取成功;
-
由于鎖會(huì)被獲取
n
次,那么只有鎖在被釋放同樣的n
次之后,該鎖才算是完全釋放成功。
ReentrantLock
支持兩種鎖:公平鎖和非公平鎖。何謂公平性,是針對(duì)獲取鎖而言的,如果一個(gè)鎖是公平的,那么鎖的獲取順序就應(yīng)該符合請(qǐng)求上的絕對(duì)時(shí)間順序,滿足FIFO
。
10、ReadWriteLock 是什么?
答: 如果使用 ReentrantLock
,可能本身是為了防止線程 A
在寫數(shù)據(jù)、線程 B
在讀數(shù)據(jù)造成的數(shù)據(jù)不一致,但這樣,如果線程 C
在讀數(shù)據(jù)、線程 D
也在讀數(shù)據(jù),讀數(shù)據(jù)是不會(huì)改變數(shù)據(jù)的,沒(méi)有必要加鎖,但是還是加鎖了,降低了程序的性能。因?yàn)檫@個(gè),才誕生了讀寫鎖ReadWriteLock。
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-741466.html
ReadWriteLock
是一個(gè)讀寫鎖接口,讀寫鎖是用來(lái)提升并發(fā)程序性能的鎖分離技術(shù),ReentrantReadWriteLock
是 ReadWriteLock
接口的一個(gè)具體實(shí)現(xiàn),實(shí)現(xiàn)了讀寫的分離,讀鎖是共享的,寫鎖是獨(dú)占的,讀和讀之間不會(huì)互斥,讀和寫、寫和讀、寫和寫之間才會(huì)互斥,提升了讀寫的性能。
而讀寫鎖有以下三個(gè)重要的特性:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-741466.html
- 公平選擇性: 支持非公平(默認(rèn))和公平的鎖獲取方式,吞吐量還是非公平優(yōu)于公平。
- 重進(jìn)入: 讀鎖和寫鎖都支持線程重進(jìn)入。
- 鎖降級(jí): 遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級(jí)成為讀鎖。
到了這里,關(guān)于Java——并發(fā)編程(CAS、Lock和AQS)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!