文章目錄
概述
在這篇文章中涉及到以下知識點:
- Monitor類充當Mutex使用,用來確保對代碼進行串行訪問,非常類似於synchronized關鍵字,但語義更加簡單,並且有一些有用的附加功能。
- ListenableFuture類的功能與Listenable類在Java中的功能相同,但我們可以註冊一個回調方法,以便在Future本身完成之後運行。
- FutureCallback類允許我們訪問Future task的結果,並且允許我們針對成功或失敗的場景進行處理。
- SettableFuture、AsyncFunction和FutureFallback類是我們在處理Future實例和進行對象異步轉換時非常有用的實用工具類。
- RateLimiter類是用來限制線程訪問資源的頻率,它非常類似於一個信號量(semaphore),但是RateLimiter類不是通過線程總數限制訪問,而是根據時間限制訪問。
Synchronizing threads
雖然Java提供了在程序中運行多個線程的能力,但是有時我們需要限制訪問(同步),以便在任何給定的時間只有一個線程可以訪問代碼的一部分。Java提供了synchronized關鍵字來實現串行訪問的目標。但是使用synchronized有一些問題。首先,如果我們需要在線程上調用wait(),我們必須記住使用while循環:
while(someCondition){
try {
wait();
} catch (InterruptedException e) {
//In this case we don't care, but we may want
//to propagate with Thread.interrupt()
}
}
其次,如果我們有多個條件可以導致線程進入等待狀態,那麼我們必須調用notifyAll(),因爲我們無法針對特定的條件通知線程。使用notifyAll()而不是notify()是不太理想的,因爲它會喚醒所有線程來爭奪鎖,而最終只有一個線程會獲得鎖。Java 5引入了ReentrantLock類和創建Condition的能力。我們可以通過使用ReentrantLock.newCondition()方法來實現更細粒度控制。現在可以用Condition.signal()調用(類似於notify())喚醒等待特定條件發生的單個線程,儘管有一個Condition.signalAll()方法具有與調用notifyAll()相同的抖動效果。但是我們仍然有一些違反直覺的while循環需要處理:
while(list.isEmpty()){
Condition.await();
}
幸運的是,Guava有解決這個問題的辦法----Monitor
Monitor
針對上面的問題,Guava的Monitor類爲我們提供了一種解決方案。它允許多種conditions,並且通過從顯式通知系統切換到隱式通知系統,完全消除了通知所有線程的可能性。讓我們來看一個例子:
public class MonitorSample {
private List<String> list = new ArrayList<String>();
private static final int MAX_SIZE = 10;
//創建Monitor實例
private Monitor monitor = new Monitor();
//利用Monitor實例構造Guard實例。
private Monitor.Guard listBelowCapacity = new Monitor.Guard(monitor) {
//Guard類有一個抽象的isSatisfied方法,返回值爲boolean。(這兒表示如果集合的大小小於10那麼就返回true)
@Override
public boolean isSatisfied() {
return list.size() < MAX_SIZE;
}
};
public void addToList(String item) throws InterruptedException {
//只有在滿足 Guard condition evaluates to true 的時候線程才允許進入
//enterWhen方法允許線程在滿足Guard condition時進入代碼塊。另外值得注意的是,我們並沒有顯式地發送任何線程信號;它完全隱含在被滿足的Guard condition中。
monitor.enterWhen(listBelowCapacity);
try {
list.add(item);
} finally {
//離開時需要釋放monitor
monitor.leave();
}
}
}
Monitor explained
當一個線程進入一個Monitor塊時,它被認爲佔用了該Monitor實例,並且一旦該線程離開,它就不再佔用Monitor塊。任何時候只有一個線程可以進入Monitor塊。該語義與使用synchronized或ReentrantLocks相同;如果一個線程已經進入Monitor塊,那麼其他線程必須等到當前線程釋放鎖,或者通過monitor.leave離開監視器塊才能進入Monitor塊。同一個線程可以任意次數地進出同一個Monitor塊,但是每次進入後面必須跟着一個離開。
Monitor best practice
對於Monitor返回boolean值的方法應該始終被使用在包含包含try/finally塊的 if 語句中,以確保線程始終能夠退出Monitor塊。
if (monitor.enterIf(guardCondition)) {
try {
doWork();
} finally {
monitor.leave();
}
}
對於不返回任何值的Monitor方法,方法的調用應該緊跟着try/finally塊。
monitor.enterWhen(guardCondition);
try {
doWork();
} finally {
monitor.leave()
}