要想實現多個線程之間的協同,如:線程執行前後順序、獲取某個線程執行的結果等等。涉及到線程之間的相互通訊,分爲下面四類:java
public class MainTest { public static void main(String[] args) { // 線程1 - 寫入數據 new Thread(() -> { try { while (true) { Files.write(Paths.get("test.log"), content = "當前時間" + String.valueOf(System.currentTimeMillis())); Thread.sleep(1000L); } } catch (Exception e) { e.printStackTrace(); } }).start(); // 線程2 - 讀取數據 new Thread(() -> { try { while (true) { Thread.sleep(1000L); byte[] allBytes = Files.readAllBytes(Paths.get("test.log")); System.out.println(new String(allBytes)); } } catch (Exception e) { e.printStackTrace(); } }).start(); } }
public class MainTest { // 共享變量 public static String content = "空"; public static void main(String[] args) { // 線程1 - 寫入數據 new Thread(() -> { try { while (true) { content = "當前時間" + String.valueOf(System.currentTimeMillis()); Thread.sleep(1000L); } } catch (Exception e) { e.printStackTrace(); } }).start(); // 線程2 - 讀取數據 new Thread(() -> { try { while (true) { Thread.sleep(1000L); System.out.println(content); } } catch (Exception e) { e.printStackTrace(); } }).start(); } }
JDK中對於須要多線程協做完成某一任務的場景,提供了對應API支持。api
多線程協做的典型場景是:生產者-消費者模型。(線程阻塞、線程喚醒)網絡
示例:線程1去買包子,沒有包子,則再也不執行。線程2生產出包子,通知線程-1繼續執行。多線程
做用:調用suspend掛起目標線程,經過resume能夠恢復線程執行。ide
/** 包子店 */ public static Object baozidian = null; /** 正常的suspend/resume */ public void suspendResumeTest() throws Exception { // 啓動線程 Thread consumerThread = new Thread(() -> { if (baozidian == null) { // 若是沒包子,則進入等待 System.out.println("一、進入等待"); Thread.currentThread().suspend(); } System.out.println("二、買到包子,回家"); }); consumerThread.start(); // 3秒以後,生產一個包子 Thread.sleep(3000L); baozidian = new Object(); consumerThread.resume(); System.out.println("三、通知消費者"); }
被棄用的主要緣由是,容易寫出不死鎖的代碼。因此用wait/notify和park/unpark機制對它進行替代this
一、同步代碼中使用線程
/** 死鎖的suspend/resume。 suspend並不會像wait同樣釋放鎖,故此容易寫出死鎖代碼 */ public void suspendResumeDeadLockTest() throws Exception { // 啓動線程 Thread consumerThread = new Thread(() -> { if (baozidian == null) { // 若是沒包子,則進入等待 System.out.println("一、進入等待"); // 當前線程拿到鎖,而後掛起 synchronized (this) { Thread.currentThread().suspend(); } } System.out.println("二、買到包子,回家"); }); consumerThread.start(); // 3秒以後,生產一個包子 Thread.sleep(3000L); baozidian = new Object(); // 爭取到鎖之後,再恢復consumerThread synchronized (this) { consumerThread.resume(); } System.out.println("三、通知消費者"); }
二、suspend比resume後執行code
/** 致使程序永久掛起的suspend/resume */ public void suspendResumeDeadLockTest2() throws Exception { // 啓動線程 Thread consumerThread = new Thread(() -> { if (baozidian == null) { System.out.println("一、沒包子,進入等待"); try { // 爲這個線程加上一點延時 Thread.sleep(5000L); } catch (InterruptedException e) { e.printStackTrace(); } // 這裏的掛起執行在resume後面 Thread.currentThread().suspend(); } System.out.println("二、買到包子,回家"); }); consumerThread.start(); // 3秒以後,生產一個包子 Thread.sleep(3000L); baozidian = new Object(); consumerThread.resume(); System.out.println("三、通知消費者"); consumerThread.join(); }
這些方法只能由同一對象鎖的持有者線程調用,也就是寫在同步塊裏面,不然會拋出IllegalMonitorStateException異常。
wait方法致使當前線程等待,加入該對象的等待集合中,而且放棄當前持有的對象鎖。
notify/notifyAll方法喚醒一個或全部正在等待這個對象鎖的線程。
注意:雖然會wait自動解鎖,可是對順序有要求,若是在notify被調用以後,纔開始wait方法的調用,線程會永遠處於WAITING狀態。對象
/** 正常的wait/notify */ public void waitNotifyTest() throws Exception { // 啓動線程 new Thread(() -> { synchronized (this) { while (baozidian == null) { // 若是沒包子,則進入等待 try { System.out.println("一、進入等待"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println("二、買到包子,回家"); }).start(); // 3秒以後,生產一個包子 Thread.sleep(3000L); baozidian = new Object(); synchronized (this) { this.notifyAll(); System.out.println("三、通知消費者"); } }
形成死鎖的示例blog
/** 會致使程序永久等待的wait/notify */ public void waitNotifyDeadLockTest() throws Exception { // 啓動線程 new Thread(() -> { if (baozidian == null) { // 若是沒包子,則進入等待 try { Thread.sleep(5000L); } catch (InterruptedException e1) { e1.printStackTrace(); } synchronized (this) { try { System.out.println("一、進入等待"); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println("二、買到包子,回家"); }).start(); // 3秒以後,生產一個包子 Thread.sleep(3000L); baozidian = new Object(); synchronized (this) { this.notifyAll(); System.out.println("三、通知消費者"); } }
線程調用park則等待「許可」,unpark方法爲指定線程提供「許可(permit)」
不要求park和unpark方法的調用順序。
屢次調用unpark以後,再調用park,線程會直接運行。
但不會疊加,也就是說,連續屢次調用park方法,第一次會拿到"許可"直接運行,後續調用會進入等待。
/** 正常的park/unpark */ public void parkUnparkTest() throws Exception { // 啓動線程 Thread consumerThread = new Thread(() -> { while (baozidian == null) { // 若是沒包子,則進入等待 System.out.println("一、進入等待"); LockSupport.park(); } System.out.println("二、買到包子,回家"); }); consumerThread.start(); // 3秒以後,生產一個包子 Thread.sleep(3000L); baozidian = new Object(); LockSupport.unpark(consumerThread); System.out.println("三、通知消費者"); }
形成死鎖的示例
/** 死鎖的park/unpark */ public void parkUnparkDeadLockTest() throws Exception { // 啓動線程 Thread consumerThread = new Thread(() -> { if (baozidian == null) { // 若是沒包子,則進入等待 System.out.println("一、進入等待"); // 當前線程拿到鎖,而後掛起 synchronized (this) { LockSupport.park(); } } System.out.println("二、買到包子,回家"); }); consumerThread.start(); // 3秒以後,生產一個包子 Thread.sleep(3000L); baozidian = new Object(); // 爭取到鎖之後,再恢復consumerThread synchronized (this) { LockSupport.unpark(consumerThread); } System.out.println("三、通知消費者"); }
警告!以前代碼中用if語句來判斷,是否進入等待狀態,是錯誤的!
官方建議應該循環中檢查等待條件,緣由是處於等待狀態的線程可能會收到錯誤警報和僞喚醒,若是不在循環中檢查等待條件,程序就會在沒有知足結束條件的狀況下退出。
僞喚醒是指線程並不是由於notify、notifyall、unpark等api調用而喚醒,是更底層緣由致使的。