作者:小傅哥 博客:https://
沉淀、分享、成長(zhǎng),讓自己和他人都能有所收獲!😄
一、前言
Java學(xué)多少才能找到工作?
最近經(jīng)常有小伙伴問(wèn)我,以為我的經(jīng)驗(yàn)來(lái)看,學(xué)多少夠,好像更多的是看你的野心有多大。如果你只是想找個(gè)10k以內(nèi)的二線城市的工作,那還是比較容易的。也不需要學(xué)數(shù)據(jù)結(jié)構(gòu)、也不需要會(huì)算法、也需要懂源碼、更不要有多少項(xiàng)目經(jīng)驗(yàn)。
但反之我遇到一個(gè)國(guó)內(nèi)大學(xué)TOP2畢業(yè)的娃,這貨兼職是Offer收割機(jī):騰訊、阿里、字節(jié)還有國(guó)外新加坡的工作機(jī)會(huì)等等,薪資待遇也是賊高,可能超過(guò)你對(duì)白菜價(jià)的認(rèn)知。上學(xué)無(wú)用、學(xué)習(xí)無(wú)用,純屬扯淡!
你能在這條路上能付出的越多,能努力的越早,收獲就會(huì)越大!
二、面試題
謝飛機(jī),小記,剛?cè)ザ屠萃昴_放松的飛機(jī),因?yàn)槟涂艘m子丟了,罵罵咧咧的赴約面試官。
面試官 :咋了,飛機(jī),怎么看上去不高興。
謝飛機(jī) :沒(méi)事,沒(méi)事,我心思我學(xué)的 synchronized 呢!
面試官 :那正好,飛機(jī)你會(huì)鎖嗎?
謝飛機(jī) :啊。。。我沒(méi)去會(huì)所呀!!!你咋知道
面試官 :我說(shuō) Java 鎖,你想啥呢!你了解公平鎖嗎,知道怎么實(shí)現(xiàn)的嗎,給我說(shuō)說(shuō)!
謝飛機(jī) :公平鎖!?嗯,是不 ReentrantLock 中用到了,我怎么感覺(jué)似乎有印象,但是不記得了。
面試官 :哎,回家搜搜 CLH 吧!
三、ReentrantLock 和 公平鎖
1. ReentrantLock 介紹
鑒于上一篇小傅哥已經(jīng)基于,HotSpot虛擬機(jī)源碼分析 synchronized 實(shí)現(xiàn)和相應(yīng)核心知識(shí)點(diǎn),本來(lái)想在本章直接介紹下 ReentrantLock。但因?yàn)?ReentrantLock 知識(shí)點(diǎn)較多,因此會(huì)分幾篇分別講解,突出每一篇重點(diǎn),避免豬八戒吞棗。
介紹 :ReentrantLock 是一個(gè)可重入且獨(dú)占式鎖,具有與 synchronized 監(jiān)視器(monitor enter、monitor exit)鎖基本相同的行為和語(yǔ)意。但與 synchronized 相比,它更加靈活、強(qiáng)大、增加了輪訓(xùn)、超時(shí)、中斷等高級(jí)功能以及可以創(chuàng)建公平和非公平鎖。
2. ReentrantLock 知識(shí)鏈條
ReentrantLock 是基于 Lock 實(shí)現(xiàn)的可重入鎖,所有的 Lock 都是基于 AQS 實(shí)現(xiàn)的,AQS 和 Condition 各自維護(hù)不同的對(duì)象,在使用 Lock 和 Condition 時(shí),其實(shí)就是兩個(gè)隊(duì)列的互相移動(dòng)。它所提供的共享鎖、互斥鎖都是基于對(duì) state 的操作。而它的可重入是因?yàn)閷?shí)現(xiàn)了同步器 Sync,在 Sync 的兩個(gè)實(shí)現(xiàn)類中,包括了公平鎖和非公平鎖。
這個(gè)公平鎖的具體實(shí)現(xiàn),就是我們本章節(jié)要介紹的重點(diǎn),了解什么是公平鎖、公平鎖的具體實(shí)現(xiàn)。學(xué)習(xí)完基礎(chǔ)的知識(shí)可以更好的理解 ReentrantLock
3. ReentrantLock 公平鎖代碼
3.1 初始化
ReentrantLock lock = new ReentrantLock ( true ) ; // true:公平鎖
lock. lock ( ) ;
try {
// todo
} finally {
lock. unlock ( ) ;
}
初始化構(gòu)造函數(shù)入?yún)?#xff0c;選擇是否為初始化公平鎖。 其實(shí)一般情況下并不需要公平鎖,除非你的場(chǎng)景中需要保證順序性。 使用 ReentrantLock 切記需要在 finally 中關(guān)閉,lock.unlock()。
3.2 公平鎖、非公平鎖,選擇
public ReentrantLock ( boolean fair) {
sync = fair ? new FairSync ( ) : new NonfairSync ( ) ;
}
構(gòu)造函數(shù)中選擇公平鎖(FairSync)、非公平鎖(NonfairSync)。
3.3 hasQueuedPredecessors
static final class FairSync extends Sync {
protected final boolean tryAcquire ( int acquires) {
final Thread current = Thread. currentThread ( ) ;
int c = getState ( ) ;
if ( c == 0 ) {
if ( ! hasQueuedPredecessors ( ) &&
compareAndSetState ( 0 , acquires) ) {
setExclusiveOwnerThread ( current) ;
return true ;
}
}
. . .
}
}
公平鎖和非公平鎖,主要是在方法 tryAcquire 中,是否有 !hasQueuedPredecessors() 判斷。
3.4 隊(duì)列首位判斷
public final boolean hasQueuedPredecessors ( ) {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
( ( s = h. next) == null || s. thread != Thread. currentThread ( ) ) ;
}
在這個(gè)判斷中主要就是看當(dāng)前線程是不是同步隊(duì)列的首位,是:true、否:false 這部分就涉及到了公平鎖的實(shí)現(xiàn),CLH(Craig,Landin andHagersten)。三個(gè)作者的首字母組合
四、什么是公平鎖
公平鎖就像是馬路邊上的衛(wèi)生間,上廁所需要排隊(duì)。當(dāng)然如果有人不排隊(duì),那么就是非公平鎖了,比如領(lǐng)導(dǎo)要先上。
CLH 是一種基于單向鏈表的高性能、公平的自旋鎖。AQS中的隊(duì)列是CLH變體的虛擬雙向隊(duì)列(FIFO),AQS是通過(guò)將每條請(qǐng)求共享資源的線程封裝成一個(gè)節(jié)點(diǎn)來(lái)實(shí)現(xiàn)鎖的分配。
為了更好的學(xué)習(xí)理解 CLH 的原理,就需要有實(shí)踐的代碼。接下來(lái)一 CLH 為核心分別介紹4種公平鎖的實(shí)現(xiàn),從而掌握最基本的技術(shù)棧知識(shí)。
五、公平鎖實(shí)現(xiàn)
1. CLH
1.1 看圖說(shuō)話
1.2 代碼實(shí)現(xiàn)
public class CLHLock implements Lock {
private final ThreadLocal< CLHLock. Node> prev;
private final ThreadLocal< CLHLock. Node> node;
private final AtomicReference< CLHLock. Node> tail = new AtomicReference < > ( new CLHLock. Node ( ) ) ;
private static class Node {
private volatile boolean locked;
}
public CLHLock ( ) {
this . prev = ThreadLocal. withInitial ( ( ) - > null) ;
this . node = ThreadLocal. withInitial ( CLHLock. Node: : new ) ;
}
@Override
public void lock ( ) {
final Node node = this . node. get ( ) ;
node. locked = true ;
Node pred_node = this . tail. getAndSet ( node) ;
this . prev. set ( pred_node) ;
// 自旋
while ( pred_node. locked) ;
}
@Override
public void unlock ( ) {
final Node node = this . node. get ( ) ;
node. locked = false ;
this . node. set ( this . prev. get ( ) ) ;
}
}
1.3 代碼講解
CLH(Craig,Landin and Hagersten),是一種基于鏈表的可擴(kuò)展、高性能、公平的自旋鎖。
在這段代碼的實(shí)現(xiàn)過(guò)程中,相當(dāng)于是虛擬出來(lái)一個(gè)鏈表結(jié)構(gòu),由 AtomicReference 的方法 getAndSet 進(jìn)行銜接。getAndSet 獲取當(dāng)前元素,設(shè)置新的元素
lock()
通過(guò) this.node.get() 獲取當(dāng)前節(jié)點(diǎn),并設(shè)置 locked 為 true。 接著調(diào)用 this.tail.getAndSet(node),獲取當(dāng)前尾部節(jié)點(diǎn) pred_node,同時(shí)把新加入的節(jié)點(diǎn)設(shè)置成尾部節(jié)點(diǎn)。 之后就是把 this.prev 設(shè)置為之前的尾部節(jié)點(diǎn),也就相當(dāng)于鏈路的指向。 最后就是自旋 while (pred_node.locked),直至程序釋放。
unlock()
釋放鎖的過(guò)程就是拆鏈,把釋放鎖的節(jié)點(diǎn)設(shè)置為false node.locked = false。 之后最重要的是把當(dāng)前節(jié)點(diǎn)設(shè)置為上一個(gè)節(jié)點(diǎn),這樣就相當(dāng)于把自己的節(jié)點(diǎn)拆下來(lái)了,等著垃圾回收。
CLH隊(duì)列鎖的優(yōu)點(diǎn)是空間復(fù)雜度低,在SMP(Symmetric Multi-Processor)對(duì)稱多處理器結(jié)構(gòu)(一臺(tái)計(jì)算機(jī)由多個(gè)CPU組成,并共享內(nèi)存和其他資源,所有的CPU都可以平等地訪問(wèn)內(nèi)存、I/O和外部中斷)效果還是不錯(cuò)的。但在 NUMA(Non-Uniform Memory Access) 下效果就不太好了,這部分知識(shí)可以自行擴(kuò)展。
2. MCSLock
2.1 代碼實(shí)現(xiàn)
public class MCSLock implements Lock {
private AtomicReference< MCSLock. Node> tail = new AtomicReference < > ( null) ;
;
private ThreadLocal< MCSLock. Node> node;
private static class Node {
private volatile boolean locked = false ;
private volatile Node next = null;
}
public MCSLock ( ) {
node = ThreadLocal. withInitial ( Node: : new ) ;
}
@Override
public void lock ( ) {
Node node = this . node. get ( ) ;
Node preNode = tail. getAndSet ( node) ;
if ( null == preNode) {
node. locked = true ;
return ;
}
node. locked = false ;
preNode. next = node;
while ( ! node. locked) ;
}
@Override
public void unlock ( ) {
Node node = this . node. get ( ) ;
if ( null != node. next) {
node. next. locked = true ;
node. next = null;
return ;
}
if ( tail. compareAndSet ( node, null) ) {
return ;
}
while ( node. next == null) ;
}
}
2.1 代碼講解
MCS 來(lái)自于發(fā)明人名字的首字母: John Mellor-Crummey和Michael Scott。
它也是一種基于鏈表的可擴(kuò)展、高性能、公平的自旋鎖,但與 CLH 不同。它是真的有下一個(gè)節(jié)點(diǎn) next,添加這個(gè)真實(shí)節(jié)點(diǎn)后,它就可以只在本地變量上自旋,而 CLH 是前驅(qū)節(jié)點(diǎn)的屬性上自旋。
因?yàn)樽孕?jié)點(diǎn)的不同,導(dǎo)致 CLH 更適合于 SMP 架構(gòu)、MCS 可以適合 NUMA 非一致存儲(chǔ)訪問(wèn)架構(gòu)。你可以想象成 CLH 更需要線程數(shù)據(jù)在同一塊內(nèi)存上效果才更好,MCS 因?yàn)槭窃诒镜曜兞孔赃x,所以無(wú)論數(shù)據(jù)是否分散在不同的CPU模塊都沒(méi)有影響。
代碼實(shí)現(xiàn)上與 CLH 沒(méi)有太多差異,這里就不在敘述了,可以看代碼學(xué)習(xí)。
3. TicketLock
3.1 看圖說(shuō)話
3.2 代碼實(shí)現(xiàn)
public class TicketLock implements Lock {
private AtomicInteger serviceCount = new AtomicInteger ( 0 ) ;
private AtomicInteger ticketCount = new AtomicInteger ( 0 ) ;
private final ThreadLocal< Integer> owner = new ThreadLocal < > ( ) ;
@Override
public void lock ( ) {
owner. set ( ticketCount. getAndIncrement ( ) ) ;
while ( serviceCount. get ( ) != owner. get ( ) ) ;
}
@Override
public void unlock ( ) {
serviceCount. compareAndSet ( owner. get ( ) , owner. get ( ) + 1 ) ;
owner. remove ( ) ;
}
}
3.3 代碼講解
TicketLock 就像你去銀行、呷哺給你的一個(gè)排號(hào)卡一樣,叫到你號(hào)你才能進(jìn)去。屬于嚴(yán)格的公平性實(shí)現(xiàn),但是多處理器系統(tǒng)上,每個(gè)進(jìn)程/線程占用的處理器都在讀寫(xiě)同一個(gè)變量,每次讀寫(xiě)操作都需要進(jìn)行多處理間的緩存同步,非常消耗系統(tǒng)性能。
代碼實(shí)現(xiàn)上也比較簡(jiǎn)單,lock() 中設(shè)置擁有者的號(hào)牌,并進(jìn)入自旋比對(duì)。unlock() 中使用 CAS 進(jìn)行解鎖操作,并處理移除。
六、總結(jié)
ReentrantLock 是基于 Lock 實(shí)現(xiàn)的可重入鎖,對(duì)于公平鎖 CLH 的實(shí)現(xiàn),只是這部分知識(shí)的冰山一角,但有這一腳 ,就可以很好熱身便于后續(xù)的學(xué)習(xí)。 ReentrantLock 使用起來(lái)更加靈活,可操作性也更大,但一定要在 finally 中釋放鎖,目的是保證在獲取鎖之后,最終能夠被釋放。同時(shí)不要將獲取鎖的過(guò)程寫(xiě)在 try 里面。 公平鎖的實(shí)現(xiàn)依據(jù)不同場(chǎng)景和SMP、NUMA的使用,會(huì)有不同的優(yōu)劣效果。在實(shí)際的使用中一般默認(rèn)會(huì)選擇非公平鎖,即使是自旋也是耗費(fèi)性能的,一般會(huì)用在較少等待的線程中,避免自旋時(shí)過(guò)長(zhǎng)。
七、系列推薦
synchronized 解毒,剖析源碼深度分析! 面試官,ThreadLocal 你要這么問(wèn),我就掛了! 掃盲java.util.Collections工具包,學(xué)習(xí)排序、二分、洗牌、旋轉(zhuǎn)算法 HashMap核心知識(shí),擾動(dòng)函數(shù)、負(fù)載因子、擴(kuò)容鏈表拆分 Netty+JavaFx實(shí)戰(zhàn):仿桌面版微信聊天!