《java7核心技術與最佳實踐》讀書筆記之 multi-thread (3)

    在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);
    }
}
相關文章
相關標籤/搜索