并发学习笔记 (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)了類,這些類足夠使用。
 但是需要知道如何使用它們,以及這些類背后的理論。JUC包教程
用synchonized:可以保證在同一時(shí)間只有一個線程可以執(zhí)行 return ++count:
public class Counter{private int count = 0;public int inc(){synchronized(this){return ++count;}} }以下的Counter類用Lock代替synchronized 達(dá)到同樣的目的:
 lock() 方法會對 Lock 實(shí)例對象進(jìn)行加鎖,因此所有其他對該對象調(diào)用 lock() 方法的線程都會被阻塞,直到該 Lock 對象的 unlock() 方法被調(diào)用。
那么問題來了, Lock類是怎么設(shè)計(jì)的?
Lock 類的設(shè)計(jì)
一個Lock類的簡單實(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() 中返回(也稱作虛假喚醒),這個線程會重新去檢查 isLocked 條件以決定當(dāng)前是否可以安全地繼續(xù)執(zhí)行還是需要重新保持等待,而不是認(rèn)為線程被喚醒了就可以安全地繼續(xù)執(zhí)行了。如果 isLocked 為 false,當(dāng)前線程會退出 while(isLocked) 循環(huán),并將 isLocked 設(shè)回 true,讓其它正在調(diào)用 lock() 方法的線程能夠在 Lock 實(shí)例上加鎖。
當(dāng)線程完成了臨界區(qū)(位于 lock() 和 unlock() 之間)中的代碼,就會調(diào)用 unlock()。執(zhí)行 unlock() 會重新將 isLocked 設(shè)置為 false,并且通知(喚醒)其中一個(若有的話)在 lock() 方法中調(diào)用了 wait() 函數(shù)而處于等待狀態(tài)的線程。
鎖的可重入性
synchronized 同步塊是可重入的。這意味著: 如果一個java線程進(jìn)入了代碼中的同步塊synchonzied block,并因此獲得了該同步塊使用的同步對象對應(yīng)的管程monitor object上的鎖那么這個線程可以進(jìn)入由同一個管程對象所同步的另一個 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();} }一個線程是否被允許退出 lock() 方法是由 while 循環(huán)(自旋鎖)中的條件決定的。當(dāng)前的判斷條件是只有當(dāng) isLocked 為 false 時(shí) lock 操作才被允許,而沒有考慮是哪個線程鎖住了它。
 所以需要對Lock的設(shè)計(jì)做出如下修改,才能可重入。
注意到現(xiàn)在的 while 循環(huán)(自旋鎖)也考慮到了已鎖住該 Lock 實(shí)例的線程。如果當(dāng)前的鎖對象沒有被加鎖 (isLocked = false),或者當(dāng)前調(diào)用線程已經(jīng)對該 Lock 實(shí)例加了鎖,那么 while 循環(huán)就不會被執(zhí)行,調(diào)用 lock() 的線程就可以退出該方法(譯者注:“被允許退出該方法” 在當(dāng)前語義下就是指不會調(diào)用 wait() 而導(dǎo)致阻塞)。
除此之外,我們需要記錄同一個線程重復(fù)對一個鎖對象加鎖的次數(shù)。否則,一次 unblock() 調(diào)用就會解除整個鎖,即使當(dāng)前鎖已經(jīng)被加鎖過多次。在 unlock() 調(diào)用沒有達(dá)到對應(yīng) lock() 調(diào)用的次數(shù)之前,我們不希望鎖被解除。
現(xiàn)在這個 Lock 類就是可重入的了。
鎖的公平性
Starvation and Fairness 饑餓和公平一個線程因?yàn)槠渌€程長期占有CPU而自己獲得不到,這種狀態(tài)稱為Starvation. 解決線程饑餓的方法是公平機(jī)制fairness公平機(jī)制,讓所有線程都能公平的有機(jī)會去獲得CPU。
導(dǎo)致饑餓的原因
Java 的同步代碼區(qū)也是一個導(dǎo)致饑餓的因素。Java 的同步代碼區(qū)對哪個線程允許進(jìn)入的次序沒有任何保障。這就意味著理論上存在一個試圖進(jìn)入該同步區(qū)的線程處于被永久堵塞的風(fēng)險(xiǎn),因?yàn)槠渌€程總是能持續(xù)地先于它獲得訪問,這即是 “饑餓” 問題,而一個線程被 “饑餓致死” 正是因?yàn)樗貌坏?CPU 運(yùn)行時(shí)間的機(jī)會
Java's synchronized code blocks can be another cause of starvation.
如果多個線程處在 wait() 方法執(zhí)行上,而對其調(diào)用 notify() 不會保證哪一個線程會獲得喚醒,任何線程都有可能處于繼續(xù)等待的狀態(tài)。因此存在這樣一個風(fēng)險(xiǎn):一個等待線程從來得不到喚醒,因?yàn)槠渌却€程總是能被獲得喚醒。
這里細(xì)說一下:多線程通過共享一個object對象,來調(diào)用對象的wait/notifyAll 來導(dǎo)致線程等待或者喚醒; 每次一個線程進(jìn)入同步塊,其他所有線程陷入等待狀態(tài);然后active線程調(diào)用notifyALL()函數(shù)喚醒所有等待線程,所有線程競爭,只有一個線程競爭成功,獲得CPU執(zhí)行。競爭失敗的線程處于就緒狀態(tài),長期競爭失敗的線程就會饑餓。
線程之間的對資源(object)競爭導(dǎo)致的饑餓,為了避免競爭,所以想辦法一次喚醒一個線程。也就是下面講的FairLock 公平鎖機(jī)制。
Implementing Fairness in Java
使用鎖lock來代替同步塊synchonized block
每一個調(diào)用 lock() 的線程都會進(jìn)入一個隊(duì)列,當(dāng)解鎖后,只有隊(duì)列里的第一個線程 (隊(duì)首)被允許鎖住 Fairlock 實(shí)例,所有其它的線程都將處于等待狀態(tài),直到他們處于隊(duì)列頭部。
 公平鎖實(shí)現(xiàn)機(jī)制:為每一個線程創(chuàng)建一個專屬鎖對象(而非多個線程共享一個對象,來wait/notify()),然后用一個隊(duì)列來管理這些鎖對象,嘗試加鎖的線程會在各自的對象上等待,當(dāng)一個線程unlock的時(shí)候,只通知隊(duì)列頭的鎖對象,以喚醒其對應(yīng)的線程
為了讓這個 Lock 類具有可重入性,我們需要對它做一點(diǎn)小的改動:
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)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 《 Spring1之第二次站立会议(重发
 - 下一篇: ueditor1.4.3配置过程(包含单