面試官:重入鎖的機(jī)制了解嗎?
點(diǎn)擊“藍(lán)字”,關(guān)注,置頂公眾號(hào)
每日技術(shù)干貨,第一時(shí)間送達(dá)!
首先我們這里提到的鎖,是把所需要的代碼塊,資源,或數(shù)據(jù)鎖上,在操作他們的時(shí)候只允許一個(gè)線程去做操作。最終結(jié)果是為了保證cpu計(jì)算結(jié)果的正確性。
對不可重入鎖的理解:
public class Test{
Lock lock = new Lock();
public void methodA(){
lock.lock();
...........;
methodB();
...........;
lock.unlock();
}
public void methodB(){
lock.lock();
...........;
lock.unlock();
}
}當(dāng)A方法獲取lock鎖去鎖住一段需要做原子性操作的B方法時(shí),如果這段B方法又需要鎖去做原子性操作,那么A方法就必定要與B方法出現(xiàn)死鎖。這種會(huì)出現(xiàn)問題的重入一把鎖的情況,叫不可重入鎖。
A方法需要等B方法執(zhí)行完才能解鎖,但是B方法想執(zhí)行完代碼又必須要lock鎖來加鎖。A的鎖未解鎖前,其他代碼塊無法使用此鎖來加鎖。這是由這個(gè)不可重入鎖決定的。
不可重入鎖:
public class Lock{
private boolean isLocked = false;
public synchronized void lock() throws InterruptedException{
while(isLocked){
wait();
}
isLocked = true;
}
public synchronized void unlock(){
isLocked = false;
notify();
}
}那么平時(shí)我們又有需要重入一把鎖的需求!!!!比如A方法是個(gè)原子性操作,但它有需要調(diào)用B方法的原子性操作,他們還爭搶的是同一個(gè)臨界資源,因此需要同一把鎖來加鎖(ps:爭搶同一臨界資源的實(shí)質(zhì)就是對同一把鎖的爭搶),歡迎關(guān)注我們,公號(hào)IT碼徒。
針對此情況,就有了可重入鎖的概念:
可重入鎖的實(shí)現(xiàn):
public class Lock{
boolean isLocked = false;
Thread lockedBy = null;
int lockedCount = 0;
public synchronized void lock()
throws InterruptedException{
Thread thread = Thread.currentThread();
while(isLocked && lockedBy != thread){
wait();
}
isLocked = true;
lockedCount++;
lockedBy = thread;
}
public synchronized void unlock(){
if(Thread.currentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}
}可以看見代碼的核心概念是:
首先解釋lockedBy:顧名思義,臨界資源被哪個(gè)線程鎖住了。
加鎖時(shí),先獲取當(dāng)前線程。(識(shí)別誰需要鎖)
Thread thread = Thread.currentThread();
判斷:當(dāng)臨界資源已被鎖上,但當(dāng)前請求鎖的線程又不是之前鎖上臨界資源的線程。那么當(dāng)前請求鎖的線程需要等待。
while(isLocked && lockedBy != thread){
wait();
}注意上面是個(gè)while,并且是個(gè)wait,因此當(dāng)請求線程請求不到鎖的時(shí)候,就wait了。
當(dāng)時(shí)當(dāng)while不滿足有的3種情況:
A:當(dāng)前鎖沒有線程使用.
B:當(dāng)前鎖有線程使用,當(dāng)前請求鎖的線程就是現(xiàn)在正在使用鎖的線程。
C:當(dāng)前鎖沒有線程使用,當(dāng)前請求鎖的線程就是現(xiàn)在正在使用鎖的線程。(不可能出現(xiàn),鎖0沒有被用,哪還有線程使用鎖)
來看看
A:沒有線程使用:
那么:
isLocked = true;
lockedCount++;
lockedBy = thread;當(dāng)前請求鎖的線程先把鎖加上,然后把上鎖次數(shù)+1,然后把自己(本線程)賦值給lockedBy,以說明當(dāng)前誰用了這把鎖方便之后重入的時(shí)候做while判斷。
再來看解鎖:
public synchronized void unlock(){
if(Thread.currentThread() == this.lockedBy){
lockedCount--;
if(lockedCount == 0){
isLocked = false;
notify();
}
}
}首先看看要求解鎖的線程是不是當(dāng)前用鎖的線程。不是則什么也不做。(當(dāng)然不能隨意讓其他的線程一執(zhí)行unlock代碼就能解鎖使用啊。那這樣相當(dāng)于誰都有一把鑰匙了,這里這個(gè)判斷也就是說明解鎖的必須是加鎖的)
如果要求解鎖的就是加鎖的線程。
那么把加鎖次數(shù)減一。
然后在判斷加鎖次數(shù)有沒有變?yōu)?。
變?yōu)?說明,這個(gè)鎖已經(jīng)完全解鎖了。鎖上標(biāo)識(shí)islocked可以復(fù)位了。
并且隨機(jī)喚醒某個(gè)被wait()等待的線程 :notify()
這就是重入鎖的設(shè)計(jì)。另外,關(guān)注Java技術(shù)精選公眾號(hào),回復(fù)“資料”,送你面試題寶典和大量視頻資源下載!
它和不可重入鎖的設(shè)計(jì)不同之處:
不可重入鎖:只判斷這個(gè)鎖有沒有被鎖上,只要被鎖上申請鎖的線程都會(huì)被要求等待。實(shí)現(xiàn)簡單
可重入鎖:不僅判斷鎖有沒有被鎖上,還會(huì)判斷鎖是誰鎖上的,當(dāng)就是自己鎖上的時(shí)候,那么他依舊可以再次訪問臨界資源,并把加鎖次數(shù)加一。
設(shè)計(jì)了加鎖次數(shù),以在解鎖的時(shí)候,可以確保所有加鎖的過程都解鎖了,其他線程才能訪問。不然沒有加鎖的參考值,也就不知道什么時(shí)候解鎖?解鎖多少次?才能保證本線程已經(jīng)訪問完臨界資源了可以喚醒其他線程訪問了。實(shí)現(xiàn)相對復(fù)雜。
總結(jié)
這個(gè)重入的概念就是,拿到鎖的代碼能不能多次以不同的方式訪問臨界資源而不出現(xiàn)死鎖等相關(guān)問題。經(jīng)典之處在于判斷了需要使用鎖的線程是否為加鎖的線程。如果是,則擁有重(chong)入的能力。
往期推薦
