7.1? AbstractQueuedSynchronizer -AQS
底層實(shí)現(xiàn)了雙向鏈表削罩,是隊(duì)列的一種實(shí)現(xiàn)方式
對(duì)象創(chuàng)建以后其狀態(tài)就不能修改
底層是雙向鏈表,隊(duì)列的一種實(shí)現(xiàn)
sync queue:同步隊(duì)列糊昙,head節(jié)點(diǎn)主要負(fù)責(zé)后面的調(diào)度
Condition queue:單向鏈表辛掠,不是必須的的,也可以有多個(gè)
設(shè)計(jì)原理
使用Node實(shí)現(xiàn)FIFO隊(duì)列,可以用于構(gòu)建鎖或者其他同步裝置的基礎(chǔ)框架
利用了一個(gè)int類型標(biāo)示狀態(tài)萝衩,有一個(gè)state的成員變量回挽,表示獲取鎖的線程數(shù)(0沒有線程獲取鎖,1有線程獲取鎖猩谊,大于1表示重入鎖的數(shù)量)千劈,和一個(gè)同步組件ReentrantLock,
使用方法是繼承牌捷,基于模板方法
子類通過繼承并通過實(shí)現(xiàn)它的方法管理其狀態(tài){acquire和release}的方法操作狀態(tài)
可以實(shí)現(xiàn)排它鎖和共享鎖的模式(獨(dú)占墙牌、共享,子類只能實(shí)現(xiàn)其中一個(gè))
具體實(shí)現(xiàn)的思路
1.首先 AQS內(nèi)部維護(hù)了一個(gè)CLH隊(duì)列暗甥,來管理鎖
線程嘗試獲取鎖喜滨,如果獲取失敗,則將等待信息等包裝成一個(gè)Node結(jié)點(diǎn)撤防,加入到同步隊(duì)列Sync queue里
3.不斷重新嘗試獲取鎖(當(dāng)前結(jié)點(diǎn)為head的直接后繼才會(huì) 嘗試)虽风,如果獲取失敗,則會(huì)阻塞自己寄月,直到被喚醒
4.當(dāng)持有鎖的線程釋放鎖的時(shí)候辜膝,會(huì)喚醒隊(duì)列中的后繼線程
AQS同步組件
CountDownLatch(閉鎖,通過一個(gè)計(jì)數(shù)保證線程是否需要一直阻塞 )
Semaphore(控制同一時(shí)間并發(fā)線程的數(shù)目)
CyclicBarrier(與CountDownLatch 相識(shí) 阻塞線程剥懒,可以重置計(jì)數(shù)器)
ReentrantLock
Condition
FutureTask
CountDownLatch
同步阻塞類内舟,可以完成阻塞線程的功能
*&&&& CountDownLatch :閉鎖,通過一個(gè)計(jì)數(shù)初橘,判斷線程是否阻塞
&&&&? Semaphore:控制并發(fā)線程的數(shù)目
7.2??CountDownLatch?
同步輔組類验游,完成阻塞當(dāng)前線程的功能,給定了一個(gè)計(jì)數(shù)器保檐,原子操作耕蝉,計(jì)數(shù)器不能重置。
調(diào)用await()方法會(huì)使線程處于阻塞狀態(tài)夜只,直到其他線程調(diào)用CountDown()方法時(shí)垒在,才繼續(xù)執(zhí)行
當(dāng)計(jì)數(shù)器變?yōu)?的時(shí)候,所有等待的線程才會(huì)繼續(xù)執(zhí)行
使用場景:查詢需要等待某個(gè)條件完成后才能繼續(xù)執(zhí)行后續(xù)操作(Ex:并行計(jì)算)拆分任務(wù)
7.3 Semaphore
并發(fā)訪問控制線程個(gè)數(shù)(同步機(jī)制)扔亥,? ?提供了兩個(gè)方法场躯,實(shí)現(xiàn)有限大小的鏈表大小
semaphore.acquire(); // 獲取一個(gè)許可
semaphore.release(); // 釋放一個(gè)許可
semaphore.acquire(n);//獲取多個(gè)許可
semaphore.release(n); // 釋放n個(gè)許可
使用場景:僅能提供有限訪問的資源,比如數(shù)據(jù)庫連接數(shù)
tryAcquire())//嘗試獲取一個(gè)許可
tryAcquire 四個(gè)帶參方法
?1 tryAcquire(long timeout, TimeUnit unit)
?2??tryAcquire()
3??tryAcquire(int permits)
4??boolean tryAcquire(int permits, long timeout, TimeUnit unit)
7.4? CyclicBarrier
運(yùn)行一組線程等待到一個(gè)公共的屏障點(diǎn)旅挤,實(shí)現(xiàn)多個(gè)線程相互等待踢关,當(dāng)每一個(gè)線程都就緒后,才執(zhí)行下去,通過計(jì)數(shù)器實(shí)現(xiàn)的
多線程計(jì)算數(shù)據(jù),最后合并的場景
CyclicBarrier 與CountDownLatch 的區(qū)別
1??CountDownLatch 的計(jì)數(shù)器只能使用一次,CyclicBarrier 可以使用reset()方法重置
2?CountDownLatch 實(shí)現(xiàn)一個(gè)或者n個(gè)線程需要等待其他線程執(zhí)行某項(xiàng)操作后才能繼續(xù)執(zhí)行?
?CyclicBarrier? 實(shí)現(xiàn)多個(gè)線程了多個(gè)線程相互等待诱告,知道多個(gè)線程都滿足了某個(gè)條件以后才繼續(xù)執(zhí)行
描述的多個(gè)線程內(nèi)部的關(guān)系儒搭,多個(gè)線程都調(diào)用await()方法后才繼續(xù)向下執(zhí)行
提供方法獲取阻塞線程的個(gè)數(shù)吠架,知道阻塞的線程是否中斷
CyclicBarrier? 對(duì)象調(diào)用await() 等待多個(gè)線程都滿足條件后,在往下面執(zhí)行
//定義有5個(gè)線程同步等待
1) private static CyclicBarrierbarrier =new CyclicBarrier(5);
2 )
在5個(gè)線程都滿足條件后搂鲫,先執(zhí)行?log.info("callback is running"); 在執(zhí)行以后的代碼
private static CyclicBarrierbarrier =new CyclicBarrier(5, () -> {
log.info("callback is running");
});
7.5 ReentrantLock 與鎖
java? 兩類鎖: 1?synchronized?
? ? ? ? ? ? ? ? ? ? ? ?2 JUC的 ReentrantLock?
?ReentrantLock 與synchronized 的區(qū)別
1 &&&? 可重入性 兩者都是可重入鎖 傍药,同一線程進(jìn)入一次 鎖的計(jì)數(shù)器就自增1 ,鎖的計(jì)數(shù)器下降為0 時(shí)才釋放鎖
2 &&&? synchronized 是依賴jvm實(shí)現(xiàn)的(操作系統(tǒng)實(shí)現(xiàn)魂仍,難查源碼)怔檩,ReentrantLock 是依賴jdk實(shí)現(xiàn)的(用戶實(shí)現(xiàn))
3 &&&? 兩者性能差不多 ,推薦使用synchronized 蓄诽,synchronized 優(yōu)化借鑒了CAS技術(shù)薛训,用戶態(tài)解決加鎖問題避免進(jìn)入內(nèi)核態(tài) 使線程阻塞
4 &&&? synchronized? 更方便它是編譯器保證鎖的加鎖和釋放的,ReentrantLock 手工釋放和加鎖仑氛,在finally釋放鎖
鎖的細(xì)膩度和靈活度?ReentrantLock 更好?
ReentrantLock? 的獨(dú)有的功能
1? ReentrantLock?可指定是公平鎖和非公平鎖??synchronized 只能是非公平鎖
公平鎖(先等待的線程先獲得鎖)
2 提供了一個(gè)Condition類乙埃,可以分組喚醒需要喚醒的線程
synchronized? 喚醒一個(gè)要不全部喚醒
3? 提供能夠中斷等待鎖的線程的機(jī)制,lock.lockInterruptibly()
ReentrantLock 實(shí)現(xiàn)是一種自旋鎖锯岖,通過循環(huán)調(diào)用cas操作自加操作介袜,避免了線程進(jìn)入內(nèi)核態(tài)發(fā)生阻塞?
synchronized 不會(huì)忘記釋放鎖
ReentrantLock 函數(shù)方法
tryLock():僅在調(diào)用時(shí)鎖定未被另外一個(gè)線程保持的情況下獲取鎖定
tryLock(long timeout, TimeUnit unit) 如果鎖定在給定的時(shí)間內(nèi)沒有被另一個(gè)線程保持,且當(dāng)前線程沒有被中斷出吹,則獲取這個(gè)鎖定
lockInterruptibly() 當(dāng)前線程如果沒有中斷就獲取鎖定遇伞,如果已經(jīng)中斷就拋出異常
isLocked() 查詢當(dāng)前此鎖定是否由任意線程保持,
ReentrantReadWriteLock? ?
?在沒有任何 讀寫鎖(ReadWrite)*的情況下才能取得寫鎖(Write)
StampedLock?
版本和模式兩個(gè)部分組成
控制鎖的三種方式:
1 寫
2 讀
3 樂觀讀
鎖獲取的方法是一個(gè)數(shù)字捶牢,用鎖的狀態(tài)控制相關(guān)鎖的狀態(tài)的訪問
數(shù)字0 表示沒有寫鎖
讀鎖分為 悲觀鎖 和樂觀鎖
對(duì)吞吐量有巨大的改進(jìn)鸠珠,特別是讀線程多的場景中下
StampedLock? 對(duì)于加鎖容易誤用其他的方法
Condition?
package com.mmall.concurrency.example.lock;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class LockExample6 {
public static void main(String[] args) {
ReentrantLock reentrantLock =new ReentrantLock();
? ? ? ? Condition condition = reentrantLock.newCondition();
? ? ? ? new Thread(() -> {
try {
reentrantLock.lock(); // 線程加入到AQS的等待隊(duì)列中
? ? ? ? ? ? ? ? log.info("wait signal"); // 1
? ? ? ? ? ? ? ? condition.await(); //調(diào)用await()方法后 線程從AQS對(duì)列中溢出(鎖的釋放),進(jìn)入到condition的等待隊(duì)列中
? ? ? ? ? ? }catch (InterruptedException e) {
e.printStackTrace();
? ? ? ? ? ? }
log.info("get signal"); // 4
? ? ? ? ? ? reentrantLock.unlock();
? ? ? ? }).start();
? ? ? ? new Thread(() -> {
reentrantLock.lock();
? ? ? ? ? ? log.info("get lock"); // 2
? ? ? ? ? ? try {
Thread.sleep(3000);
? ? ? ? ? ? }catch (InterruptedException e) {
e.printStackTrace();
? ? ? ? ? ? }
condition.signalAll();
? ? ? ? ? ? log.info("send signal ~ "); // 3
? ? ? ? ? ? reentrantLock.unlock();
? ? ? ? }).start();
? ? }
}
result:
- wait signal
- get lock
- send signal ~
- get signal
總結(jié)
1 只有少量競爭者的時(shí)候秋麸,synchronized是比較好的選擇?
2 競爭者不少渐排,線程的數(shù)量可以預(yù)估的,ReentrantLock 是一個(gè)比較好的鎖實(shí)現(xiàn)