淺析Google Guava中concurrent下的Monitor和Future特性

關於Monitor

A synchronization abstraction supporting waiting on arbitrary boolean conditions

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

public class SafeBox<V> {
    private V value;
    public synchronized V get() throws InterruptedException {
        while (value == null) {
            wait();//vaule 爲空 等待
        }
        //獲得cpu,取出value
        V result = value;
        value = null;
        notifyAll();//喚醒其他wait方法
        return result;
    }
    public synchronized void set(V newValue) throws InterruptedException {
        while (value != null) {
            wait();//等待
        }
        //被喚醒後,給value賦值
        value = newValue;
        //喚醒
        notifyAll();
    }
}

上面的代碼可能還不足以說明原生jdk中純在的問題,但是原生的wait、notify無法做到更加精細的喚醒操作,而Condition它更強大的地方在於:能夠更加精細的控制多線程的休眠與喚醒。對於同一個鎖,我們可以創建多個Condition,就是多個監視器的意思。在不同的情況下使用不同的Condition。

例如,假如多線程讀/寫同一個緩衝區:當向緩衝區中寫入數據之後,喚醒”讀線程”;當從緩衝區讀出數據之後,喚醒”寫線程”

如果採用Object類中的wait(), notify(), notifyAll()實現該緩衝區,當向緩衝區寫入數據之後需要喚醒”讀線程”時,不可能通過notify()或notifyAll()明確的指定喚醒”讀線程”,而只能通過notifyAll喚醒所有線程(但是notifyAll無法區分喚醒的線程是讀線程,還是寫線程)。當所有的線程都被喚醒,這裏會再次產生一個鎖的競爭. 但是,通過Condition,就能明確的指定喚醒讀線程。我們在編程的時候可以指定喚醒任何一個線程,如下

public class SafeBox<V> {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition valuePresent = lock.newCondition();//read condition
    private final Condition valueAbsent = lock.newCondition();//write condition
    private V value;

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

看吧有些事情是原生的wait、nofity而不能做到的,現在向大家展示Google guava庫中的monitor。提供更多的API,更豐富的功能

public class MonitorSample {
        private Queue<Integer> queue = new LinkedList<Integer>();
        private Monitor monitor = new Monitor();
        //put 的Guard,重寫方法,可以設置什麼情況下返回ture,true就表示放行
        //這裏表示當queue的大小在3個以下的時候可是進入
        private Monitor.Guard put = new Monitor.Guard(monitor) {
            @Override
            public boolean isSatisfied() {
                return queue.size() < 3;
            }
        };
        //只要queue裏面有值,我們就可以取
        private Monitor.Guard get = new Monitor.Guard(monitor) {
            @Override
            public boolean isSatisfied() {
                return queue.size() > 0;
            }
        };

        public void set(int value) throws InterruptedException {
            //這種方式和try lock的方式相近.當時可以看出來,
            //比condition更加直觀,每一個guard都可以設置一個 門檻,來放行,
            //當任何一個guard達到了條件,就會被喚醒.比如在size等於2的時候,做一些操作,
            //添加一個guard然後觸發.這比condition更加好用
            //而且提供了更多的API
            monitor.enterWhen(put);
            try {
                queue.add(value);
            } finally {
                monitor.leave();
            }
        }
        public int get() throws InterruptedException {
            monitor.enterWhen(get);
            try {
                return queue.poll();
            } finally {
                monitor.leave();
            }
        }
}        

提供更多的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時喚醒任意線程。此方法設計用來檢測系統狀態。

Future編程

我們都知道jdk給了我們異步接口,叫做Future<V>,我們一般在寫異步操作的時候一般都是這樣

Future<String> future1 = executorService.submit(new Callable<String>() {
            public String call() throws Exception {
            //模擬方法調用耗時
                Thread.currentThread().sleep(3000);
                return "first";
            }
        });

這裏我們會立即得到一個Future對象,但是我們的方法調用,並不能馬上得到值,只有當我們調用Future#get()方法的時候,會導致一直阻塞到方法的值被得到,假如我們有3個RPC方法需要調用,RPC-1耗時3秒。RPC-2耗時2秒。RPC-1耗時1秒,如果我們通過傳統的方法調用,就會耗時6s,必須等RPC-1調用完成之後,才能進行RPC-2,之後才能進行RPC-1.
如果按照我們異步的思想,其實我們的耗時應該是max(RPC-1,RPC-2,RPC-3)=3s.所以我們利用傳統的future接口可以利用future#get的阻塞,拿到3個調用的最長耗時。
但是如果我們希望當future,剛好返回的時候,我們就能調用呢。就是我們常說的異步回調。如果我們用傳統的future,只能去輪訓future的計算狀態來判斷future是否計算完成,有了Google guava的包裝,讓一切都變得非常的簡單。ListeningExecutorService是guava實現的可添加監聽事件的executor,ListenableFuture則繼承了Future接口,添加了addListener接口

public interface ListenableFuture<V> extends Future<V> {
    void addListener(Runnable listener, Executor executor);
}

我們只需要像下面這樣做,就可以實現回調了

//ListeningExecutorService是guava實現的可添加監聽事件的executor
        ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(3));
        ListenableFuture<String> future1 = executorService.submit(new Callable<String>() {
            public String call() throws Exception {
                Thread.currentThread().sleep(3000);//模擬延遲
                return "first";
            }
        });
        future1.addListener(new Runnable() {
            public void run() {
                //回調函數
                System.out.println("do something");
            }
        },executorService);

Futures類還提供了callback方法,可以得到future的返回值的回調方法

 Futures.addCallback(ListenableFuture, new FutureCallback<String>(){
           public void onSuccess(String result) {
               //調用成功
           }

           public void onFailure(Throwable t) {
               //調用失敗
           }
       });

Futures類還提供了一個allasList方法,把所有的ListenableFuture組合成一個list來處理,這樣方便我們有多個回調操作的時候對返回值做一些聚合處理

 //構成一個返回值數組
        ListenableFuture<List<Object>> listListenableFuture = Futures.allAsList((Iterable<? extends ListenableFuture<?>>) Arrays.asList(future1, future2, future3));
        Futures.addCallback(listListenableFuture, new FutureCallback<List<Object>>() {
            public void onSuccess(List<Object> result) {
                //list 和數組裏的下標是相互對應的,可以做一些聚合操作
            }
            public void onFailure(Throwable t) {
                System.out.println("fair");
            }
        });

然們來看看callback的源碼實現


 public static <V> void addCallback(final ListenableFuture<V> future,
      final FutureCallback<? super V> callback, Executor executor) {
    Preconditions.checkNotNull(callback);
    Runnable callbackListener = new Runnable() {
      @Override
      public void run() {
        final V value;
        try {
      //FutureCallback接口下的方法就是onSuccess和onFailure,然後重新開啓了一個線程c
      //allbackListener,裏面調用了getUninterruptibly方法,
      //那麼這個方法是幹嘛的呢?見下文

          value = getUninterruptibly(future);
        } catch (ExecutionException e) {
          callback.onFailure(e.getCause());
          return;
        } catch (RuntimeException e) {
          callback.onFailure(e);
          return;
        } catch (Error e) {
          callback.onFailure(e);
          return;
        }
        callback.onSuccess(value);
      }
    };
    future.addListener(callbackListener, executor);
  }

先筆者說過了,如果我們要實現回調,還有一種方式就是,不斷的去輪訓future的計算狀態是否是已完成。那麼我們在getUninterruptibly方法裏面看到了這個,雖然get會阻塞,但是getUninterruptibly是在callbackListener這個新的線程當中的

  public static <V> V getUninterruptibly(Future<V> future)
      throws ExecutionException {
    boolean interrupted = false;
    try {
      while (true) {
      //一直輪訓future#get.困惑爲什麼不直接調用future的計算狀態
        try {
          return future.get();
        } catch (InterruptedException e) {
          interrupted = true;
        }
      }
    } finally {
      if (interrupted) {
        Thread.currentThread().interrupt();
      }
    }
  }

最後調用了future.addListener(callbackListener, executor);這個方法,最簡單的回調,只是現在的callbackListener線程裏面我們可以得到success和failure狀態做一些事情。詳情參加guava的源碼吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章