并发学习笔记 (5)
tutorials site
Locks in java
Locks (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)了類(lèi),這些類(lèi)足夠使用。
但是需要知道如何使用它們,以及這些類(lèi)背后的理論。JUC包教程
用synchonized:可以保證在同一時(shí)間只有一個(gè)線程可以執(zhí)行 return ++count:
public class Counter{private int count = 0;public int inc(){synchronized(this){return ++count;}} }以下的Counter類(lèi)用Lock代替synchronized 達(dá)到同樣的目的:
lock() 方法會(huì)對(duì) Lock 實(shí)例對(duì)象進(jìn)行加鎖,因此所有其他對(duì)該對(duì)象調(diào)用 lock() 方法的線程都會(huì)被阻塞,直到該 Lock 對(duì)象的 unlock() 方法被調(diào)用。
那么問(wèn)題來(lái)了, Lock類(lèi)是怎么設(shè)計(jì)的?
Lock 類(lèi)的設(shè)計(jì)
一個(gè)Lock類(lèi)的簡(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), 又被稱(chēng)為spin lock自旋鎖。當(dāng) isLocked 為 true 時(shí),調(diào)用 lock() 的線程在 wait() 調(diào)用上阻塞等待。為防止該線程沒(méi)有收到 notify() 調(diào)用也從 wait() 中返回(也稱(chēng)作虛假喚醒),這個(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 somethinglock.unlock();} }一個(gè)線程是否被允許退出 lock() 方法是由 while 循環(huán)(自旋鎖)中的條件決定的。當(dāng)前的判斷條件是只有當(dāng) isLocked 為 false 時(shí) lock 操作才被允許,而沒(méi)有考慮是哪個(gè)線程鎖住了它。
所以需要對(duì)Lock的設(shè)計(jì)做出如下修改,才能可重入。
注意到現(xiàn)在的 while 循環(huán)(自旋鎖)也考慮到了已鎖住該 Lock 實(shí)例的線程。如果當(dāng)前的鎖對(duì)象沒(méi)有被加鎖 (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)被加鎖過(guò)多次。在 unlock() 調(diào)用沒(méi)有達(dá)到對(duì)應(yīng) lock() 調(diào)用的次數(shù)之前,我們不希望鎖被解除。
現(xiàn)在這個(gè) Lock 類(lèi)就是可重入的了。
鎖的公平性
Starvation and Fairness 饑餓和公平一個(gè)線程因?yàn)槠渌€程長(zhǎng)期占有CPU而自己獲得不到,這種狀態(tài)稱(chēng)為Starvation. 解決線程饑餓的方法是公平機(jī)制fairness公平機(jī)制,讓所有線程都能公平的有機(jī)會(huì)去獲得CPU。
導(dǎo)致饑餓的原因
Java 的同步代碼區(qū)也是一個(gè)導(dǎo)致饑餓的因素。Java 的同步代碼區(qū)對(duì)哪個(gè)線程允許進(jìn)入的次序沒(méi)有任何保障。這就意味著理論上存在一個(gè)試圖進(jìn)入該同步區(qū)的線程處于被永久堵塞的風(fēng)險(xiǎn),因?yàn)槠渌€程總是能持續(xù)地先于它獲得訪問(wèn),這即是 “饑餓” 問(wèn)題,而一個(gè)線程被 “饑餓致死” 正是因?yàn)樗貌坏?CPU 運(yùn)行時(shí)間的機(jī)會(huì)
Java's synchronized code blocks can be another cause of starvation.
如果多個(gè)線程處在 wait() 方法執(zhí)行上,而對(duì)其調(diào)用 notify() 不會(huì)保證哪一個(gè)線程會(huì)獲得喚醒,任何線程都有可能處于繼續(xù)等待的狀態(tài)。因此存在這樣一個(gè)風(fēng)險(xiǎn):一個(gè)等待線程從來(lái)得不到喚醒,因?yàn)槠渌却€程總是能被獲得喚醒。
這里細(xì)說(shuō)一下:多線程通過(guò)共享一個(gè)object對(duì)象,來(lái)調(diào)用對(duì)象的wait/notifyAll 來(lái)導(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來(lái)代替同步塊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è)專(zhuān)屬鎖對(duì)象(而非多個(gè)線程共享一個(gè)對(duì)象,來(lái)wait/notify()),然后用一個(gè)隊(duì)列來(lái)管理這些鎖對(duì)象,嘗試加鎖的線程會(huì)在各自的對(duì)象上等待,當(dāng)一個(gè)線程unlock的時(shí)候,只通知隊(duì)列頭的鎖對(duì)象,以喚醒其對(duì)應(yīng)的線程
為了讓這個(gè) Lock 類(lèi)具有可重入性,我們需要對(duì)它做一點(diǎn)小的改動(dòng):
javapublic class FairLock {private boolean isLocked = false;private Thread lockingThread = null;private List<QueueObject> waitingThreads =new ArrayList<QueueObject>();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();}} }總結(jié)
以上是生活随笔為你收集整理的并发学习笔记 (5)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 《 Spring1之第二次站立会议(重发
- 下一篇: ueditor1.4.3配置过程(包含单