用一個例子來講明Object對象中的wait方法和notifyAll方法的使用。java
首先定義一個消息類,用於封裝數據,以供讀寫線程進行操做:服務器
1 /** 2 * 消息 3 * 4 * @author syj 5 */ 6 public class Message { 7 8 private String msg; 9 10 public String getMsg() { 11 return msg; 12 } 13 14 public void setMsg(String msg) { 15 this.msg = msg; 16 } 17 }
建立一個讀線程,從Message對象中讀取數據,若是沒有數據,就使用 wait() 方法一直阻塞等待結果(等待後面的寫線程寫入數據):app
1 /** 2 * 讀線程 3 * 4 * @author syj 5 */ 6 public class Reader implements Runnable { 7 8 private Message message; 9 10 public Reader(Message message) { 11 this.message = message; 12 } 13 14 @Override 15 public void run() { 16 synchronized (message) { 17 try { 18 // 務必加上該判斷,不然可能會因某個讀線程在寫線程的 notifyAll() 以後執行, 19 // 這將致使該讀線程永遠沒法被喚醒,程序會一直被阻塞 20 if (message.getMsg() == null) { 21 message.wait();// 等待被 message.notify() 或 message.notifyAll() 喚醒 22 } 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 // 讀取 message 對象中的數據 27 System.out.println(Thread.currentThread().getName() + " - " + message.getMsg()); 28 } 29 } 30 }
建立一個寫線程,往Message對象中寫數據,寫入成功就調用 message.notifyAll() 方法來喚醒在 message.wait() 上阻塞的線程(上面的讀線程將被喚醒,讀線程解除阻塞繼續執行):框架
1 import java.util.UUID; 2 3 /** 4 * 寫線程 5 * 6 * @author syj 7 */ 8 public class Writer implements Runnable { 9 10 private Message message; 11 12 public Writer(Message message) { 13 this.message = message; 14 } 15 16 @Override 17 public void run() { 18 synchronized (message) { 19 try { 20 Thread.sleep(1000L);// 模擬業務耗時 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 // 向 message 對象中寫數據 25 message.setMsg(Thread.currentThread().getName() + ":" + UUID.randomUUID().toString().replace("-", "")); 26 message.notifyAll();// 喚醒全部 message.wait() 27 } 28 } 29 }
注意,讀線程的等待和寫線程的喚醒,必須調用同一個對象上的wait或notifyAll方法,而且對這兩個方法的調用必定要放在synchronized塊中。dom
這裏的讀線程和寫線程使用的同一個對象是message,讀線程調用message.wait()方法進行阻塞,寫線程調用message.notifyAll()方法喚醒全部(由於調用message.wait()方法的可能會有對個線程,在本例中就有兩個讀線程調用了message.wait() 方法)讀線程的阻塞。異步
寫一個測試類,啓動兩個讀線程,從Message對象中讀取數據,再啓動一個寫線程,往Message對象中寫數據:ide
1 /** 2 * 測試 Object 對象中的 wait()/notifyAll() 用法 3 * 4 * @author syj 5 */ 6 public class LockApp { 7 public static void main(String[] args) { 8 Message message = new Message(); 9 new Thread(new Reader(message), "R1").start();// 讀線程 名稱 R1 10 new Thread(new Reader(message), "R2").start();// 讀線程 名稱 R2 11 new Thread(new Writer(message), "W").start();// 寫線程 名稱 W 12 } 13 }
控制檯打印結果:測試
R2 - W:4840dbd6b312489a9734414dd99a4bcb
R1 - W:4840dbd6b312489a9734414dd99a4bcb
其中R2表明第二個讀線程,R2是這個讀線程的名字。R1是第一個讀線程,線程名叫R2。後面的uui就是模擬的異步執行結果了,W表明寫線程的名字,表示數據是由寫線程寫入的。 因爲咱們只開啓一個寫線程,全部兩條數據的uuid是同一個,只不過被兩個讀線程都接收到了而已。ui
拋出一個問題:Object對象的這個特性有什麼用呢?this
它比較適合用在同步等待異步處理結果的場景中。好比,在RPC框架中,Netty服務器一般返回結果是異步的,而Netty客戶端想要拿到這個異步結果進行處理,該怎麼作呢?
下面使用僞代碼來模擬這個場景:
1 import java.util.UUID; 2 import java.util.concurrent.ConcurrentHashMap; 3 4 /** 5 * 使用 Object對象的 wait() 和 notifyAll() 實現同步等待異步結果 6 * 7 * @author syj 8 */ 9 public class App { 10 11 // 用於存放異步結果, key是請求ID, value是異步結果 12 private static ConcurrentHashMap<String, String> resultMap = new ConcurrentHashMap<>(); 13 private Object lock = new Object(); 14 15 /** 16 * 寫數據到 resultMap,寫入成功喚醒全部在 lock 對象上等待的線程 17 * 18 * @param requestId 19 * @param message 20 */ 21 public void set(String requestId, String message) { 22 resultMap.put(requestId, message); 23 synchronized (lock) { 24 lock.notifyAll(); 25 } 26 } 27 28 /** 29 * 從 resultMap 中讀數據,若是沒有數據則等待 30 * 31 * @param requestId 32 * @return 33 */ 34 public String get(String requestId) { 35 synchronized (lock) { 36 try { 37 if (resultMap.get(requestId) == null) { 38 lock.wait(); 39 } 40 } catch (InterruptedException e) { 41 e.printStackTrace(); 42 } 43 } 44 return resultMap.get(requestId); 45 } 46 47 /** 48 * 移除結果 49 * 50 * @param requestId 51 */ 52 public void remove(String requestId) { 53 resultMap.remove(requestId); 54 } 55 56 /** 57 * 測試方法 58 * 59 * @param args 60 */ 61 public static void main(String[] args) { 62 // 請求惟一標識 63 String requestId = UUID.randomUUID().toString(); 64 App app = new App(); 65 try { 66 // 模擬Netty服務端異步返回結果 67 new Thread(new Runnable() { 68 @Override 69 public void run() { 70 try { 71 Thread.sleep(2000L);// 模擬業務耗時 72 } catch (InterruptedException e) { 73 e.printStackTrace(); 74 } 75 // 寫入數據 76 app.set(requestId, UUID.randomUUID().toString().replace("-", "")); 77 } 78 }).start(); 79 80 // 模擬Netty客戶端同步等待讀取Netty服務器端返回的結果 81 String message = app.get(requestId); 82 System.out.println(message); 83 } catch (Exception e) { 84 e.printStackTrace(); 85 } finally { 86 // 結果再也不使用,必定要移除,以防止內容溢出 87 app.remove(requestId); 88 } 89 } 90 }
這裏定義了一個靜態的ConcurrentHashMap容器,來存放Netty服務器返回的異步結果,key是請求的id,value就是異步執行結果。
調用set方法能夠往容器中寫入數據(寫入請求ID和相對應的執行結果),調用get方法能夠從容器讀取數據(根據請求ID獲取對應的執行結果)。
get方法中調用lock對象的wait方法進行阻塞等待結果,set方法往容器中寫入結果以後,緊接着調用的是同一個lock對象的notifyAll方法來喚醒該lock對象上的全部wait()阻塞線程。
以此來達到同步等待獲取異步執行結果的目的。