本篇文章默認你們對synchronized
跟ReentrantLock
有必定了解。java
下面一段簡單的代碼,主要是經過3個線程對count進行累計來進行模擬多線程的場景。編程
/** * zhongxianyao */ public class Test { private static final int N = 3; private int count = 0; public void doSomething() { // 實際業務中,這裏多是遠程獲取數據之類的耗時操做 for (int i=0; i<1000_000; i++) { synchronized (this) { count ++; } } } public static void main(String[] args) throws Exception { Test test = new Test(); for (int i=0; i<N; i++) { Runnable runnable = () -> test.doSomething(); new Thread(runnable).start(); } Thread.sleep(1000); System.out.println(test.count); } }
在多線程編程中,一旦調用start()後,何時真正分配CPU時間片運行是不肯定的,運行多久也是不肯定的,因此有時候可能根據經驗,預估一下程序的運行時間,而後進行sleep,最後獲取結果。但這種方式太low了,有沒有那麼一種方式,當程序獲取到結果後進行通知呢?下面將引出今天要講的等待/通知機制。api
先來一段代碼看一下wait()/notify()的基本用法多線程
/** * zhongxianyao */ public class Test { private static final int N = 3; private int count = 0; private int finishCount = 0; public void doSomething() { for (int i=0; i<1000_000; i++) { synchronized (this) { count ++; } } synchronized (this) { finishCount ++; notify(); } } public static void main(String[] args) { Test test = new Test(); for (int i=0; i<N; i++) { Runnable runnable = () -> test.doSomething(); new Thread(runnable).start(); } synchronized (test) { try { while (test.finishCount != N) { test.wait(); } } catch (Exception e) { e.printStackTrace(); } } System.out.println(test.count); } }
結果輸出3000000
,結果是正確,是本身想要的。oracle
接口描述以下app
As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop: synchronized (obj) { while (<condition does not hold>) obj.wait(); ... // Perform action appropriate to condition }
翻譯一下:在一個論點版本中,中斷跟虛假喚醒是可能,因此這個方法應始終放在一個循環中。oop
加上一句本身的解釋:通常在項目中,一個線程不可能平白無故等待,老是須要在某種條件下進行等待,並且其餘線程喚醒這個線程的時候,可能用的是notifyAll(),數據被其餘線程消費了,這裏須要在判斷一下是否知足特定的條件再繼續運行。測試
解釋1:wait()自己設計的邏輯就是在釋放鎖進行等待,若是沒有獲取鎖,談何釋放。this
解釋2:一般在wait()的方法前面都會有while語句的判斷,在這兩條語句中會有時間間隔,可能會破壞程序,須要加上synchronized同步代碼塊來保證原子操做。
由於wait()等方法都是鎖級別操做,再者Java提供的鎖都是對象級別的而不是線程級別的,每一個對象都有鎖。若是wait()方法定義在Thread類中,線程正在等待的是哪一個鎖就不明顯了。
在上面的例子中,若是notify();
那行代碼刪除,wait()
改成wait(100)
,以下,那麼程序是否能夠獲取到正確的結果呢?
/** * zhongxianyao */ public class Test { private static final int N = 3; private int count = 0; private int finishCount = 0; public void doSomething() { for (int i=0; i<1000_000; i++) { synchronized (this) { count ++; } } synchronized (this) { finishCount ++; //notify(); } } public static void main(String[] args) { Test test = new Test(); for (int i=0; i<N; i++) { Runnable runnable = () -> test.doSomething(); new Thread(runnable).start(); } synchronized (test) { try { while (test.finishCount != N) { test.wait(100); } } catch (Exception e) { e.printStackTrace(); } } System.out.println(test.count); } }
運行結果是3000000
,是正確的結果,看了一下文檔,發現這個字段跟直覺理解的不同,直覺告訴我,這個是最長等多久,等過久了就InterruptedException
,結果不是。這個方法設置的時間,是說等待多久就喚醒本身。
下面的代碼,把前一個例子中的synchronized代碼塊,換成了lock()/unlock,notify()換成了condition.signal(),wait()換成了condition.await()。運行結果也是正確的。
/** * zhongxianyao */ public class Test { private static final int N = 3; private int count = 0; private int finishCount = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void doSomething() { for (int i=0; i<1000_000; i++) { synchronized (this) { count ++; } } lock.lock(); finishCount ++; if (finishCount == N) { condition.signal(); } lock.unlock(); } public static void main(String[] args) { Test test = new Test(); for (int i=0; i<N; i++) { Runnable runnable = () -> test.doSomething(); new Thread(runnable).start(); } test.lock.lock(); try { test.condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { test.lock.unlock(); } System.out.println(test.count); } }
public class ConditionTest { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); new Thread(() -> { try { long time = System.currentTimeMillis(); lock.lock(); System.out.println("await start"); condition.await(); System.out.println("await end " + (System.currentTimeMillis() - time) + "ms"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }, "Thread-await").start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { try { lock.lock(); System.out.println("signal start"); TimeUnit.SECONDS.sleep(5); condition.signal(); System.out.println("signal end"); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println("signal unlock"); } }, "Thread-signal").start(); } }
屢次運行,結果都是同樣的,以下:
await start signal start signal end signal unlock await end 5005ms
從運行結果能夠看出,await()後,鎖就釋放了,但signal()後,鎖不釋放,必定要在unlock()以後,鎖才釋放,await()纔會往下執行。
既然喚醒了其餘線程,又不釋放鎖,能夠調整喚醒的時機。通常在實際代碼中,也是不建議signal()方法後添加邏輯,應該直接釋放鎖。
同理,上面的notify()也是在synchronized代碼塊結束後,wait()後面的語句才能真正執行。
把上面的condition.await()
改成condition.await(1, TimeUnit.SECONDS)
,而後獲取返回值,運行結果返回的是false
。
這個時候,若是把TimeUnit.SECONDS.sleep(5)
,condition.signal()
這兩行代碼順序調換一下,那麼await
的返回值就是true
。
再看到官方文檔對這個返回值的描述,以下
{@code false} if the waiting time detectably elapsed before return from the method, else {@code true}
翻譯過來,大體意思就是「若是等待時間能夠在方法返回以前檢測到返回false,不然返回true」。但實際測試結果倒是await()
被喚醒的時候,而不是方法返回的時候。