摘要:執(zhí)行會(huì)重新將設(shè)置為,并且通知喚醒其中一個(gè)若有的話在方法中調(diào)用了函數(shù)而處于等待狀態(tài)的線程。除此之外,我們需要記錄同一個(gè)線程重復(fù)對(duì)一個(gè)鎖對(duì)象加鎖的次數(shù)。競(jìng)爭(zhēng)失敗的線程處于就緒狀態(tài),長(zhǎng)期競(jìng)爭(zhēng)失敗的線程就會(huì)饑餓。
tutorials site
Locks in javaLocks (and other more advanced synchronization mechanisms) are created using synchronized blocks, so it is not like we can get totally rid of the synchronized keyword.
鎖的實(shí)現(xiàn)是利用synchonized, wait(),notify()方法實(shí)現(xiàn)的。所以不可以認(rèn)為鎖可以完全脫離synchonized實(shí)現(xiàn)。
Java包 JUC java.util.concurrent.locks 包括了很多l(xiāng)ock接口的實(shí)現(xiàn)了類,這些類足夠使用。
但是需要知道如何使用它們,以及這些類背后的理論。JUC包教程
用synchonized:可以保證在同一時(shí)間只有一個(gè)線程可以執(zhí)行 return ++count:
public class Counter{ private int count = 0; public int inc(){ synchronized(this){ return ++count; } } }
以下的Counter類用Lock代替synchronized 達(dá)到同樣的目的:
lock() 方法會(huì)對(duì) Lock 實(shí)例對(duì)象進(jìn)行加鎖,因此所有其他對(duì)該對(duì)象調(diào)用 lock() 方法的線程都會(huì)被阻塞,直到該 Lock 對(duì)象的 unlock() 方法被調(diào)用。
public class Counter{ private Lock lock = new Lock(); private int count = 0; public int inc(){ lock.lock(); int newCount = ++count; lock.unlock(); return newCount; } }
那么問題來了, Lock類是怎么設(shè)計(jì)的?
Lock 類的設(shè)計(jì)一個(gè)Lock類的簡(jiǎn)單實(shí)現(xiàn):
javapublic class Lock{ private boolean isLocked = false; public synchronized void lock() throws InterruptedException{ while(isLocked){ wait(); } isLocked = true; } public synchronized void unlock(){ isLocked = false; notify(); } }
while(isLocked) 循環(huán), 又被稱為spin lock自旋鎖。當(dāng) isLocked 為 true 時(shí),調(diào)用 lock() 的線程在 wait() 調(diào)用上阻塞等待。為防止該線程沒有收到 notify() 調(diào)用也從 wait() 中返回(也稱作虛假喚醒),這個(gè)線程會(huì)重新去檢查 isLocked 條件以決定當(dāng)前是否可以安全地繼續(xù)執(zhí)行還是需要重新保持等待,而不是認(rèn)為線程被喚醒了就可以安全地繼續(xù)執(zhí)行了。如果 isLocked 為 false,當(dāng)前線程會(huì)退出 while(isLocked) 循環(huán),并將 isLocked 設(shè)回 true,讓其它正在調(diào)用 lock() 方法的線程能夠在 Lock 實(shí)例上加鎖。
當(dāng)線程完成了臨界區(qū)(位于 lock() 和 unlock() 之間)中的代碼,就會(huì)調(diào)用 unlock()。執(zhí)行 unlock() 會(huì)重新將 isLocked 設(shè)置為 false,并且通知(喚醒)其中一個(gè)(若有的話)在 lock() 方法中調(diào)用了 wait() 函數(shù)而處于等待狀態(tài)的線程。
鎖的可重入性synchronized 同步塊是可重入的。這意味著: 如果一個(gè)java線程進(jìn)入了代碼中的同步塊synchonzied block,并因此獲得了該同步塊使用的同步對(duì)象對(duì)應(yīng)的管程monitor object上的鎖那么這個(gè)線程可以進(jìn)入由同一個(gè)管程對(duì)象所同步的另一個(gè) java 代碼塊
前面的Lock的設(shè)計(jì)就不是可重入的:
javapublic class Reentrant2{ Lock lock = new Lock(); public outer(){ lock.lock(); inner(); lock.unlock(); } public synchronized inner(){ lock.lock(); //do something lock.unlock(); } }
一個(gè)線程是否被允許退出 lock() 方法是由 while 循環(huán)(自旋鎖)中的條件決定的。當(dāng)前的判斷條件是只有當(dāng) isLocked 為 false 時(shí) lock 操作才被允許,而沒有考慮是哪個(gè)線程鎖住了它。
所以需要對(duì)Lock的設(shè)計(jì)做出如下修改,才能可重入。
javapublic class Lock{ boolean isLocked = false; Thread lockedBy = null; int lockedCount = 0; public synchronized void lock() throws InterruptedException{ Thread callingThread = Thread.currentThread(); while(isLocked && lockedBy != callingThread){ wait(); } isLocked = true; lockedCount++; lockedBy = callingThread; } public synchronized void unlock(){ if(Thread.curentThread() == this.lockedBy){ lockedCount--; if(lockedCount == 0){ isLocked = false; notify(); } } } ... }
注意到現(xiàn)在的 while 循環(huán)(自旋鎖)也考慮到了已鎖住該 Lock 實(shí)例的線程。如果當(dāng)前的鎖對(duì)象沒有被加鎖 (isLocked = false),或者當(dāng)前調(diào)用線程已經(jīng)對(duì)該 Lock 實(shí)例加了鎖,那么 while 循環(huán)就不會(huì)被執(zhí)行,調(diào)用 lock() 的線程就可以退出該方法(譯者注:“被允許退出該方法” 在當(dāng)前語(yǔ)義下就是指不會(huì)調(diào)用 wait() 而導(dǎo)致阻塞)。
除此之外,我們需要記錄同一個(gè)線程重復(fù)對(duì)一個(gè)鎖對(duì)象加鎖的次數(shù)。否則,一次 unblock() 調(diào)用就會(huì)解除整個(gè)鎖,即使當(dāng)前鎖已經(jīng)被加鎖過多次。在 unlock() 調(diào)用沒有達(dá)到對(duì)應(yīng) lock() 調(diào)用的次數(shù)之前,我們不希望鎖被解除。
現(xiàn)在這個(gè) Lock 類就是可重入的了。
鎖的公平性Starvation and Fairness 饑餓和公平
一個(gè)線程因?yàn)槠渌€程長(zhǎng)期占有CPU而自己獲得不到,這種狀態(tài)稱為Starvation. 解決線程饑餓的方法是公平機(jī)制fairness公平機(jī)制,讓所有線程都能公平的有機(jī)會(huì)去獲得CPU。
導(dǎo)致饑餓的原因高優(yōu)先級(jí)的線程占有了所有CPU處理時(shí)間,這樣低優(yōu)先級(jí)的線程獲得不到;
處于阻塞狀態(tài)的線程無限期被阻塞
Java 的同步代碼區(qū)也是一個(gè)導(dǎo)致饑餓的因素。Java 的同步代碼區(qū)對(duì)哪個(gè)線程允許進(jìn)入的次序沒有任何保障。這就意味著理論上存在一個(gè)試圖進(jìn)入該同步區(qū)的線程處于被永久堵塞的風(fēng)險(xiǎn),因?yàn)槠渌€程總是能持續(xù)地先于它獲得訪問,這即是 “饑餓” 問題,而一個(gè)線程被 “饑餓致死” 正是因?yàn)樗貌坏?CPU 運(yùn)行時(shí)間的機(jī)會(huì)
Java"s synchronized code blocks can be another cause of starvation.
處于等待狀態(tài)的對(duì)象無限期等待
如果多個(gè)線程處在 wait() 方法執(zhí)行上,而對(duì)其調(diào)用 notify() 不會(huì)保證哪一個(gè)線程會(huì)獲得喚醒,任何線程都有可能處于繼續(xù)等待的狀態(tài)。因此存在這樣一個(gè)風(fēng)險(xiǎn):一個(gè)等待線程從來得不到喚醒,因?yàn)槠渌却€程總是能被獲得喚醒。
這里細(xì)說一下:多線程通過共享一個(gè)object對(duì)象,來調(diào)用對(duì)象的wait/notifyAll 來導(dǎo)致線程等待或者喚醒; 每次一個(gè)線程進(jìn)入同步塊,其他所有線程陷入等待狀態(tài);然后active線程調(diào)用notifyALL()函數(shù)喚醒所有等待線程,所有線程競(jìng)爭(zhēng),只有一個(gè)線程競(jìng)爭(zhēng)成功,獲得CPU執(zhí)行。競(jìng)爭(zhēng)失敗的線程處于就緒狀態(tài),長(zhǎng)期競(jìng)爭(zhēng)失敗的線程就會(huì)饑餓。
線程之間的對(duì)資源(object)競(jìng)爭(zhēng)導(dǎo)致的饑餓,為了避免競(jìng)爭(zhēng),所以想辦法一次喚醒一個(gè)線程。也就是下面講的FairLock 公平鎖機(jī)制。
Implementing Fairness in Java使用鎖lock來代替同步塊synchonized block
每一個(gè)調(diào)用 lock() 的線程都會(huì)進(jìn)入一個(gè)隊(duì)列,當(dāng)解鎖后,只有隊(duì)列里的第一個(gè)線程 (隊(duì)首)被允許鎖住 Fairlock 實(shí)例,所有其它的線程都將處于等待狀態(tài),直到他們處于隊(duì)列頭部。
公平鎖實(shí)現(xiàn)機(jī)制:為每一個(gè)線程創(chuàng)建一個(gè)專屬鎖對(duì)象(而非多個(gè)線程共享一個(gè)對(duì)象,來wait/notify()),然后用一個(gè)隊(duì)列來管理這些鎖對(duì)象,嘗試加鎖的線程會(huì)在各自的對(duì)象上等待,當(dāng)一個(gè)線程unlock的時(shí)候,只通知隊(duì)列頭的鎖對(duì)象,以喚醒其對(duì)應(yīng)的線程
為了讓這個(gè) Lock 類具有可重入性,我們需要對(duì)它做一點(diǎn)小的改動(dòng):
javapublic class FairLock { private boolean isLocked = false; private Thread lockingThread = null; private ListwaitingThreads = new ArrayList (); public void lock() throws InterruptedException{ QueueObject queueObject = new QueueObject(); boolean isLockedForThisThread = true; synchronized(this){ waitingThreads.add(queueObject); } while(isLockedForThisThread){ synchronized(this){ isLockedForThisThread = isLocked || waitingThreads.get(0) != queueObject; if(!isLockedForThisThread){ isLocked = true; waitingThreads.remove(queueObject); lockingThread = Thread.currentThread(); return; } } try{ queueObject.doWait(); }catch(InterruptedException e){ synchronized(this) { waitingThreads.remove(queueObject); } throw e; } } } public synchronized void unlock(){ if(this.lockingThread != Thread.currentThread()){ throw new IllegalMonitorStateException( "Calling thread has not locked this lock"); } isLocked = false; lockingThread = null; if(waitingThreads.size() > 0){ waitingThreads.get(0).doNotify(); } } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/64366.html
摘要:每個(gè)通過網(wǎng)絡(luò)到達(dá)服務(wù)器的連接都被包裝成一個(gè)任務(wù)并且傳遞給線程池。線程池的線程會(huì)并發(fā)的處理連接上的請(qǐng)求。用線程池控制線程數(shù)量,其他線程排隊(duì)等候。實(shí)現(xiàn)包,線程池頂級(jí)接口是但是嚴(yán)格意義講并不是一個(gè)線程。此線程池支持定時(shí)以及周期性執(zhí)行任務(wù)的需求。 tutorial site1tutorial site2 一個(gè)問題: 每啟動(dòng)一個(gè)新線程都會(huì)有相應(yīng)的性能開銷(涉及到OS的交互:創(chuàng)建線程,銷毀線程...
摘要:請(qǐng)參看前一篇文章并發(fā)學(xué)習(xí)筆記一原子性可見性有序性問題六等待通知機(jī)制什么是等待通知機(jī)制當(dāng)線程不滿足某個(gè)條件,則進(jìn)入等待狀態(tài)如果線程滿足要求的某個(gè)條件后,則通知等待的線程重新執(zhí)行。經(jīng)極客時(shí)間并發(fā)編程實(shí)戰(zhàn)專欄內(nèi)容學(xué)習(xí)整理 請(qǐng)參看前一篇文章:Java 并發(fā)學(xué)習(xí)筆記(一)——原子性、可見性、有序性問題 六、等待—通知機(jī)制 什么是等待通知—機(jī)制?當(dāng)線程不滿足某個(gè)條件,則進(jìn)入等待狀態(tài);如果線程滿足要...
摘要:最后,總結(jié)一下,導(dǎo)致并發(fā)問題的三個(gè)源頭分別是原子性一個(gè)線程在執(zhí)行的過程當(dāng)中不被中斷??梢娦砸粋€(gè)線程修改了共享變量,另一個(gè)線程能夠馬上看到,就叫做可見性。 計(jì)算機(jī)的 CPU、內(nèi)存、I/O 設(shè)備的速度一直存在較大的差異,依次是 CPU > 內(nèi)存 > I/O 設(shè)備,為了權(quán)衡這三者的速度差異,主要提出了三種解決辦法: CPU 增加了緩存,均衡和內(nèi)存的速度差異 發(fā)明了進(jìn)程、線程,分時(shí)復(fù)用 CP...
閱讀 2686·2023-04-25 15:15
閱讀 1327·2021-11-25 09:43
閱讀 1614·2021-11-23 09:51
閱讀 1089·2021-11-12 10:36
閱讀 2891·2021-11-11 16:55
閱讀 966·2021-11-08 13:18
閱讀 736·2021-10-28 09:31
閱讀 2061·2019-08-30 15:47