tutorials sitehtml
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.
鎖的實現是利用synchonized, wait(),notify()方法實現的。因此不能夠認爲鎖能夠徹底脫離synchonized實現。java
Java包 JUC java.util.concurrent.locks 包括了不少lock接口的實現了類,這些類足夠使用。
可是須要知道如何使用它們,以及這些類背後的理論。JUC包教程安全
用synchonized:能夠保證在同一時間只有一個線程能夠執行 return ++count
:多線程
public class Counter{ private int count = 0; public int inc(){ synchronized(this){ return ++count; } } }
如下的Counter類用Lock代替synchronized 達到一樣的目的:
lock() 方法會對 Lock 實例對象進行加鎖,所以全部其餘對該對象調用 lock() 方法的線程都會被阻塞,直到該 Lock 對象的 unlock() 方法被調用。函數
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類是怎麼設計的?this
一個Lock類的簡單實現:線程
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)
循環, 又被稱爲spin lock
自旋鎖。當 isLocked
爲 true
時,調用 lock()
的線程在 wait()
調用上阻塞等待。爲防止該線程沒有收到 notify()
調用也從 wait()
中返回(也稱做虛假喚醒),這個線程會從新去檢查 isLocked 條件以決定當前是否能夠安全地繼續執行仍是須要從新保持等待,而不是認爲線程被喚醒了就能夠安全地繼續執行了。若是 isLocked 爲 false,當前線程會退出 while(isLocked) 循環,並將 isLocked 設回 true,讓其它正在調用 lock() 方法的線程可以在 Lock 實例上加鎖。設計
當線程完成了臨界區(位於 lock() 和 unlock() 之間)中的代碼,就會調用 unlock()。執行 unlock() 會從新將 isLocked 設置爲 false,而且通知(喚醒)其中一個(如有的話)在 lock() 方法中調用了 wait() 函數而處於等待狀態的線程。code
synchronized 同步塊是可重入的
。這意味着: 若是一個java線程進入了代碼中的同步塊synchonzied block,並所以得到了該同步塊使用的同步對象
對應的管程monitor object上的鎖那麼這個線程能夠進入由同一個管程對象所同步的另外一個 java 代碼塊
htm
前面的Lock的設計就不是可重入的:
javapublic class Reentrant2{ Lock lock = new Lock(); public outer(){ lock.lock(); inner(); lock.unlock(); } public synchronized inner(){ lock.lock(); //do something lock.unlock(); } }
一個線程是否被容許退出 lock() 方法是由 while 循環(自旋鎖)中的條件決定的。當前的判斷條件是隻有當 isLocked 爲 false 時 lock 操做才被容許,而沒有考慮是哪一個線程鎖住了它。
因此須要對Lock的設計作出以下修改,才能可重入。
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(); } } } ... }
注意到如今的 while 循環(自旋鎖)也考慮到了已鎖住該 Lock 實例的線程。若是當前的鎖對象沒有被加鎖 (isLocked = false),或者當前調用線程已經對該 Lock 實例加了鎖,那麼 while 循環就不會被執行,調用 lock() 的線程就能夠退出該方法(譯者注:「被容許退出該方法」 在當前語義下就是指不會調用 wait() 而致使阻塞)。
除此以外,咱們須要記錄同一個線程重複對一個鎖對象加鎖的次數。不然,一次 unblock() 調用就會解除整個鎖,即便當前鎖已經被加鎖過屢次。在 unlock() 調用沒有達到對應 lock() 調用的次數以前,咱們不但願鎖被解除。
如今這個 Lock 類就是可重入的了。
Starvation and Fairness 飢餓和公平
一個線程由於其餘線程長期佔有CPU而本身得到不到,這種狀態稱爲Starvation
. 解決線程飢餓的方法是公平機制fairness公平機制
,讓全部線程都能公平的有機會去得到CPU。
阻塞狀態
的線程無限期被阻塞Java's synchronized code blocks can be another cause of starvation.
等待狀態
的對象無限期等待這裏細說一下:多線程經過共享一個object對象,來調用對象的wait/notifyAll 來致使線程等待或者喚醒; 每次一個線程進入同步塊,其餘全部線程陷入等待狀態;而後active線程調用notifyALL()函數喚醒全部等待線程,全部線程競爭,只有一個線程競爭成功,得到CPU執行。競爭失敗的線程處於就緒狀態,長期競爭失敗的線程就會飢餓。
線程之間的對資源(object)競爭致使的飢餓,爲了不競爭,因此想辦法一次喚醒一個線程。也就是下面講的FairLock 公平鎖機制。
使用鎖lock來代替同步塊synchonized block
每個調用
lock()
的線程都會進入一個隊列,當解鎖後,只有隊列裏的第一個線程 (隊首)
被容許鎖住Fairlock
實例,全部其它的線程都將處於等待狀態,直到他們處於隊列頭部。
公平鎖實現機制:爲每個線程建立一個專屬鎖對象(而非多個線程共享一個對象,來wait/notify()),而後用一個隊列來管理這些鎖對象,嘗試加鎖的線程會在各自的對象上等待,當一個線程unlock的時候,只通知隊列頭的鎖對象,以喚醒其對應的線程
爲了讓這個 Lock 類具備可重入性,咱們須要對它作一點小的改動:
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(); } } }