在java平臺出現很長一段時間內,開發多thread程序只能使用java平臺提供的synchronized和volatile關鍵字,以及object類中的wait,notify和notifyall方法。以上抽象層次比較低,在開發中使用起來比較繁瑣,並且容易產生錯誤。並且線程間交互方式存在某些固定的模式,好比生產者-消費者模式和讀-寫模式。後來J2SE5.0引入juc包,提供了高層次的API,能夠知足平常開發中常見的需求。
java
高級同步機制
數據結構
雖然非阻塞方式性能要優於阻塞方式,但並不是全部場景都要採用非阻塞的方式實現,有不少狀況下仍然須要使用基於鎖機制的阻塞方式實現。juc包的locks接口就是一個鎖,能夠經過其中的lock方法獲取鎖,unlock來解鎖。使用lock接口的代碼須要保證鎖老是被釋放,通常把unlock方法放在finally代碼塊中。lock方法獲取鎖的方式相似於synchronized關鍵詞,以阻塞方式獲取鎖。另外還能夠經過trylock方法以非阻塞方式獲取鎖。若是在調用trylock方法時沒法獲取鎖,直接返回false,不會阻塞當前thread。利用trylock方法另一種重載形式能夠指定超時時間。若是指定了超時時間,當沒法獲取鎖時,當前thread會阻塞,但等待的時間不會超進指定的超時時間,同時thread也是能夠被中斷的。
性能
另一個與鎖相關的接口是ReadWriteLock.rwl接口實際上表示的是兩個鎖,一個是讀取操做相關的鎖,另外一個是寫入操做使用的排他鎖。該接口適合於解決常見的讀取-寫入問題相似場景。在沒有thread進行寫入操做時,進行讀取操做的多個thread均可以獲取讀取鎖,而進行寫入操做的thread只有在獲取寫入鎖後才能進行寫入操做。多個thread能夠同時進行讀取操做,可是同一時刻只容許一個thread進行寫入操做。在大多數狀況下,對一個數據結構的讀取操做次數要遠多於寫入操做的次數。
ui
juc。locks包中提供lock接口和readwritelock接口的基本實現,分別爲reentrantlock和reentrantreadwritelock類。這兩具類共同特徵是可重入性,即容許一個thread屢次獲取同一個鎖。reentranlock類對象能夠有一個全部者thread,表示上一次成功獲取該鎖,但尚未釋放鎖的thread。reentrantlock類的對象同時保存了全部者thread在該對象上加鎖的次數。經過getholdcount方法能夠獲取當前的加鎖次數。若是reentrantlock類的對象當前沒有全部者thread,則當前thread獲取鎖的操做會成功,加鎖次數爲1。在隨後的操做中,thread能夠再次獲取該鎖,這也是可重入的含義所在。每次加鎖操做會使加鎖次數加1,而每一次調用unlock方法釋放鎖會使加鎖次數減1。當加鎖次數變爲0,該鎖會被釋放,能夠被其餘thread獲取。
線程
在建立reentrantlock類對象時能夠經過一個額外的boolean類型參數來聲明使用更加公平的加鎖機制。在使用鎖機制會遇到一個問題是thread飢餓問題。當多個thread同時競爭某個鎖時,可能有的thread一直沒法成功獲取鎖,一直處於沒法運行狀態。thread飢餓是有些程序應該避免的問題。若是在建立reentrantlock類的對象時添加了額外的參數true,則reentrantlock會使用相對公平的鎖分配策略。當鎖處於可被獲取狀態時,在因爲嘗試獲取該鎖而處於等待狀態的thread中,等待時間最長的thread會成功獲取這個鎖。這就避免了thread飢餓問題。不帶參數的trylock方法會忽略公平模式的設置。
code
可重入鎖優點在於減小了鎖在各thread間傳遞次數,能夠提升程序的吞吐量。爲了提升程序總體吞吐量應該儘量使用可重入鎖。lock接口代替synchronized,相對應的condition接口替代object類的wait,notify和notifyall方法。使用condition接口時也須要與一個對應的lock接口實現對象關聯起來。經過lock接口的newcondition方法能夠建立新的condition接口的實現對象。在調用condition接口的方法以前,也須要使用lock接口的方法來獲取鎖。condition接口提供了多個相似object類的wait方法的方法,最基本的是await方法,調用該方法會使當前thread進行等待狀態,直到被喚醒或被中斷。另一種await方法的重載形式能夠指定超時時間。方法awaitnanos以納秒數爲單位指定超時時間,該方法返回值是剩餘等待時間的估計值。相似的awaituntil方法也能夠指定超時時間,只不過指定的不是要通過的時間而是超時發生的時間點,參數是一個java.util.date類的對象。前面幾種方法都會響應其餘thread發出的中斷請求,而awaituninterruptibly方法則不會處理中斷請求。
對象
與condition接口中等待方法相對應的是signal和signalall方法,至關於object的notify和notifyall方法,示例
繼承
Lock lock = new ReentrantLock();接口
Condition condition = lock.newCondition();隊列
lock.lock();
try{
while(logic condition){
condition.await();
}
}finally{
lock.unlock();
}
底層同步器
在一個multi thread中,thread 間可能存在多種不一樣的同步方式,一種比較常郵的需求是對有限個共享資源的同步訪問,multi thread程序中不少場景均可以抽象成這類同步方式。好比對某個監視器對象的互斥訪問,其實是多個thread在競爭惟一的一個資源。若是系統中安裝了兩臺打印機,那麼須要進行打印操做的多個thread想互競爭這兩個資源。當多個thread在等待同一個資源時,從公平的角度出發,這些thread會被放入到一個先入先出FIFO隊列中,當資源變成可用時,處於隊首的thread會獲取該資源。
若是程序中同步方式能夠抽象成對有限個資源的同步訪問,可使用juc。locks包中的abstractqueuesynchronizer類和abstractqueuedlongsynchronizer類做爲實現的基礎。這兩個類的做用是相同的,只不過前者在內部使用一個int類型的變量來維護內部狀態,然後者使用一個long類型的變量。能夠將這個內部變量理解成共享資源的個數。經過getstate,setstate和compareandset這三個方法來更新這個內部變量的值。它們都是abstract的,所以須要繼承並重寫其中包含的部分方法後才以使用。一般作法是把它們的子類做爲一個java內部類。外部的java類提供具體的同步方式。
public class SimpleResourceManager{ private final InnerSynchronizer synchronizer; private static class InnerSynchronizer extends AbstractQueuedSynchronizer{ InnerSynchronizer(int numOfResources){ setState(numOfResources); } protected int tryAcquireShared(int qcquires){ for(;;){ int available = getState(); int remaining = available - acquires; if(remaining < 0 || compareAndSetState(available,remaining)){ return remaining; } } } protected boolean tryReleaseShared(int releases){ for(;;){ int available = getState(); int next = available + releases; if(compareAndSetState(available,next)){ return true; } } } // } public SimpleResourceManager(int numOfResources){ synchronizer = new InnerSynchronizer(numOfResources); } public void acquire() throws InterruptedException { synchronizer.qcquireSharedInterruptibly(1); } public void release(){ synchronizer.releaseShared(1); } }