每日學習20170701--guava併發編制之Monitor使用

Monitor類是作爲ReentrantLock的一個替代,代碼中使用 Monitor比使用ReentrantLock更不易出錯,可讀性也更強,並且也沒有顯著的性能損失,使用Monitor甚至有潛在的性能得到優化。下面我們整體上對Monitor的源碼結構做一下梳理,總的來說也就在從jdk最原生的wait、notify.再做了一層warp。提供更加豐富的API。

原生實現

現在假設我們要對一個變量進行線程安全的讀寫操作,原生jdk代碼實現如下:

public class Semaphore {
    private String value;
    public synchronized String get() throws InterruptedException {
        while (null == this.value){
            wait();
        }
        String result = new String(this.value);
        this.value = null;
        notifyAll();
        return result;
    }
    public synchronized void set(String value) throws InterruptedException {
        while (null != this.value){
            wait();
        }
        this.value = value;
        notifyAll();
    }
}
ReentrantLock實現

現在問題來了,如果同時有多個讀線程和寫線程,原生的方式無法支持更加精細的併發控制。比如當寫線程寫入後,只希望喚醒讀線程,而原生的notifyAll方法會嘗試喚醒所有等待的線程,必然引發所有讀線程和寫線程之間的競爭。並且代碼本身表達的含義也不夠友好。如果用Condition就能很好的解決這個問題。代碼如下:

public class MultiCondition {
    private String value;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition valuePresent = lock.newCondition();//read condition
    private final Condition valueAbsent = lock.newCondition();//write condition
    public String get() throws InterruptedException {
        lock.lock();
        try {
            while (value == null) {
                //讀線程等待
                valuePresent.await();
            }
            String result = value;
            value = null;
            //value置爲null的時候,指定喚醒write condition.
            valueAbsent.signal();
            return result;
        } finally {
            lock.unlock();
        }
    }
    public void set(String newValue) throws InterruptedException {
        lock.lock();
        try {
            while (value != null) {
                //value還存在,不可以寫,寫線程等待
                valueAbsent.await();
            }
            value = newValue;
            //指定喚醒read線程,表示可讀
            valuePresent.signal();
        } finally {
            lock.unlock();
        }
    }
}
Monitor實現

這樣一來代碼就很明確的可以看出讀寫線程之間的併發調度,但是這裏的condition其實只是通過利用類似於多個信號量的方式來表示的,但是condition本身是沒有什麼實際的條件意義的。接下來看利用monitor的寫法:

public class MonitorSample {
    private String value;
    private Monitor monitor = new Monitor();
    private Monitor.Guard put = new Monitor.Guard(monitor) {
        @Override
        public boolean isSatisfied() {
            return null == value;
        }
    };
    private Monitor.Guard get = new Monitor.Guard(monitor) {
        @Override
        public boolean isSatisfied() {
            return null != value;
        }
    };
    public void set(String value) throws InterruptedException {
        monitor.enterWhen(put);
        this.value = value;
        monitor.leave();
    }
    public String get() throws InterruptedException {
        monitor.enterWhen(get);
        String result = new String(value);
        value = null;
        monitor.leave();
        return result;
    }
}

這種方式和try lock的方式相近。可以看出來,比condition更加直觀,每一個guard都可以設置一個門檻來放行,當任何一個guard達到了條件,就會被喚醒。另外moniter還提供了更多的API。

更多API
  • enter():進入到當前Monitor,無限期阻塞。
  • enterInterruptibly():進入到當前Monitor,無限期阻塞,但可能會被打斷。
  • enter(long time, TimeUnit unit):進入到當前Monitor,最多阻塞給定的時間,返回是否進入Monitor。
  • enterInterruptibly(long time, TimeUnit unit):進入到當前Monitor,最多阻塞給定的時間,但可能會被打斷,返回是否進入Monitor。
    tryEnter():如果可以的話立即進入Monitor,不阻塞,返回是否進入Monitor。
  • enterWhen(Guard guard):當Guard的isSatisfied()爲true時,進入當前Monitor,無限期阻塞,但可能會被打斷。
  • enterWhenUninterruptibly(Guard guard):當Guard的isSatisfied()爲true時,進入當前Monitor,無限期阻塞。
  • enterWhen(Guard guard, long time, TimeUnit unit):當Guard的isSatisfied()爲true時,進入當前Monitor,最多阻塞給定的時間,這個時間包括獲取鎖的時間和等待Guard satisfied的時間,但可能會被打斷。
  • enterWhenUninterruptibly(Guard guard, long time, TimeUnit unit):當Guard的isSatisfied()爲true時,進入當前Monitor,最多阻塞給定的時間,這個時間包括獲取鎖的時間和等待Guard satisfied的時間。
  • enterIf(Guard guard):如果Guard的isSatisfied()爲true,進入當前Monitor,無限期的獲得鎖,不需要等待Guard satisfied。
  • enterIfInterruptibly(Guard guard):如果Guard的isSatisfied()爲true,進入當前Monitor,無限期的獲得鎖,不需要等待Guard satisfied,但可能會被打斷。
  • enterIf(Guard guard, long time, TimeUnit unit):如果Guard的isSatisfied()爲true,進入當前Monitor,在給定的時間內持有鎖,不需要等待Guard satisfied。
  • enterIfInterruptibly(Guard guard, long time, TimeUnit unit):如果Guard的isSatisfied()爲true,進入當前Monitor,在給定的時間內持有鎖,不需要等待Guard satisfied,但可能會被打斷。
  • tryEnterIf(Guard guard):如果Guard的isSatisfied()爲true並且可以的話立即進入Monitor,不等待獲取鎖,也不等待Guard satisfied。
  • waitFor(Guard guard):等待Guard satisfied,無限期等待,但可能會被打斷,當一個線程當前佔有Monitor時,該方法纔可能被調用。
  • waitForUninterruptibly(Guard guard):等待Guard satisfied,無限期等待,當一個線程當前佔有Monitor時,該方法纔可能被調用。
  • waitFor(Guard guard, long time, TimeUnit unit):等待Guard satisfied,在給定的時間內等待,但可能會被打斷,當一個線程當前佔有Monitor時,該方法纔可能被調用。
  • waitForUninterruptibly(Guard guard, long time, TimeUnit unit):等待Guard satisfied,在給定的時間內等待,當一個線程當前佔有Monitor時,該方法纔可能被調用。
  • leave():離開當前Monitor,當一個線程當前佔有Monitor時,該方法纔可能被調用。
  • isFair():判斷當前Monitor是否使用一個公平的排序策略。
  • isOccupied():返回當前Monitor是否被任何線程佔有,此方法適用於檢測系統狀態,不適用於同步控制。
  • isOccupiedByCurrentThread():返回當前線程是否佔有當前Monitor。
  • getOccupiedDepth():返回當前線程進入Monitor的次數,如果房前線程不佔有Monitor,返回0。
  • getQueueLength():返回一個估計的等待進入Monitor的線程數量,只是一個估算值,因爲線程的數量在這個方法訪問那不數據結構的時候可能會動態改變。此方法適用於檢測系統狀態,不適用於同步控制。
  • getWaitQueueLength(Guard guard):返回一個等待給定Guard satisfied的線程估計數量, 注意,因爲超時和中斷可能發生在任何時候,所以估計只作爲一個等待線程的實際數目的上限。此方法適用於檢測系統狀態,不適用於同步控制。
  • hasQueuedThreads():返回是否有任何線程正在等待進入這個Monitor,注意,因爲取消隨時可能發生,所以返回true並不保證任何其他線程會進入這個Monitor。此方法設計用來檢測系統狀態。
  • hasQueuedThread(Thread thread):返回給定線程是否正在等待進入這個Monitor,注意,因爲取消隨時可能發生,所以返回true並不保證給定線程會進入這個Monitor。此方法設計用來檢測系統狀態。
  • hasWaiters(Guard guard):返回是否有任何線程正在等待給定Guard satisfied,注意,因爲取消隨時可能發生,所以返回true並不保證未來Guard變成satisfied時喚醒任意線程。此方法設計用來檢測系統狀態。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章