《Java 多執行緒程式設計核心技術》筆記——第3章 執行緒間通訊(三)
文章目錄
宣告:
本部落格是本人在學習《Java 多執行緒程式設計核心技術》後整理的筆記,旨在方便複習和回顧,並非用作商業用途。
本部落格已標明出處,如有侵權請告知,馬上刪除。
3.2 方法 join 的使用
在很多情況下,主執行緒建立並啟動子執行緒,如果子執行緒中要進行大量的耗時計算,主執行緒往往將早於子執行緒結束之前結束。這時,如果主執行緒想等待子執行緒執行完了再結束。比如子執行緒處理一個資料,主執行緒要取到這個資料中的值,就要用到 join() 方法了。方法 join() 的作用是等待執行緒物件銷燬。
3.2.1 學習 join 方法前的鋪墊
在介紹 join 方法之前,先來看一個實驗。
-
建立一個自定義的執行緒類
public class MyThread extends Thread { @Override public void run() { try { int secondValue = (int) (Math.random() * 10000); System.out.println(secondValue); Thread.sleep(secondValue); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
測試類
public class Run { public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); //Thread.sleep(?); System.out.println("當myThread物件執行完畢後再執行"); System.out.println("但上面程式碼的sleep的值寫多少"); System.out.println("答案是不確定"); } }
執行結果:
當myThread物件執行完畢後再執行 但上面程式碼的sleep的值寫多少 答案是不確定 5900
3.2.2 用 join() 方法來解決
-
建立一個自定義的執行緒類
public class MyThread extends Thread { @Override public void run() { try { int secondValue = (int) (Math.random() * 10000); System.out.println(secondValue); Thread.sleep(secondValue); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
測試類
public class Run { public static void main(String[] args) { try { MyThread myThread = new MyThread(); myThread.start(); myThread.join(); System.out.println("當物件 myThread 執行完畢後再執行"); } catch (InterruptedException e) { e.printStackTrace(); } } }
執行結果:
5482 當物件 myThread 執行完畢後再執行
方法 join 的作用是使所屬的執行緒物件 x 正常執行 run() 方法中的任務,而使當前執行緒 z 進行無限期阻塞,等待執行緒 x 銷燬後再繼續執行執行緒 z 後面的程式碼。
方法 join 具有使執行緒排隊執行的作用,有些類似同步的效果。join 與 synchronized 的區別是:join 內部是使用 wait() 方法進行等待的,而 synchronized 關鍵字是使用的是 “物件監視器” 原理做為同步。
3.2.3 方法 join 與異常
在 join 過程中,如果當前執行緒物件被中斷,則當前執行緒出現異常。
示例如下:
-
建立三個自定義的執行緒類
public class ThreadA extends Thread { @Override public void run() { for (int i = 0; i < Integer.MAX_VALUE; i++) { String newString = new String(); Math.random(); } } }
public class ThreadB extends Thread { @Override public void run() { try { ThreadA threadA = new ThreadA(); threadA.start(); threadA.join(); System.out.println("執行緒B在run end處列印了"); } catch (InterruptedException e) { System.out.println("執行緒B在catch處列印了"); e.printStackTrace(); } } }
public class ThreadC extends Thread { private ThreadB threadb; public ThreadC(ThreadB threadb) { this.threadb = threadb; } @Override public void run() { threadb.interrupt(); } }
-
測試類
public class Run { public static void main(String[] args) { try { ThreadB b = new ThreadB(); b.start(); Thread.sleep(500); ThreadC c = new ThreadC(b); c.start(); } catch (Exception e) { e.printStackTrace(); } } }
執行結果:
執行緒B在catch處列印了 java.lang.InterruptedException at java.lang.Object.wait(Native Method) at java.lang.Thread.join(Thread.java:1249) at java.lang.Thread.join(Thread.java:1323) at joinexception.ThreadB.run(ThreadB.java:9)
說明方法 join() 與 interrupt() 方法如果彼此相遇,則會出現異常。但程式按鈕還是呈紅色狀態,原因是執行緒 ThreadA 還在繼續執行,執行緒 ThreadA 並未出現異常,是正常執行的狀態。
3.2.4 方法 join(long) 的使用
方法 join(long) 中的引數是設定等待的時間。
示例如下:
-
建立自定義的執行緒類
public class MyThread extends Thread { @Override public void run() { super.run(); try { System.out.println("begin timer=" + System.currentTimeMillis()); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
測試類
public class Test { public static void main(String[] args) { try { MyThread myThread = new MyThread(); myThread.start(); myThread.join(2000); // Thread.sleep(2000); System.out.println("end timer= " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } }
執行結果:
begin timer=1607782653815 end timer= 1607782655815
但將 main 方法中的程式碼改成使用 sleep(2000) 方法時,執行的效果還是等待了 2 秒,執行結果如下所示。
begin timer=1607783051218
end timer= 1607783053217
那使用 join(2000) 和使用 sleep(2000) 有什麼區別呢?上面的示例中在執行效果上並沒有區別,其實區別主要還是來自於這 2 個方法對同步的處理上。
3.2.5 方法 join(long) 和 sleep(long) 的區別
方法 join(long) 的功能在內部是使用 wait(long) 方法來實現的,所以 join(long) 方法具有釋放鎖的特點。
方法 join(long) 原始碼如下:
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
從原始碼中可以瞭解到,當執行 wait(long) 方法後,當前執行緒的鎖被釋放,那麼其他執行緒就可以呼叫此執行緒中的同步方法了。
而 Thread.sleep(long) 方法卻不釋放鎖。
在下面的示例中將實驗 Thread.sleep(long) 方法具有不釋放鎖的特點。
-
建立三個自定義的執行緒類
public class ThreadA extends Thread { private ThreadB b; public ThreadA(ThreadB b) { super(); this.b = b; } @Override public void run() { try { synchronized (b) { b.start(); Thread.sleep(6000); // Thread.sleep()不釋放鎖 } } catch (InterruptedException e) { e.printStackTrace(); } } }
public class ThreadB extends Thread { @Override public void run() { try { System.out.println(" b run begin timer=" + System.currentTimeMillis()); Thread.sleep(5000); System.out.println(" b run end timer=" + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public void bService() { System.out.println("列印 b Service timer=" + System.currentTimeMillis()); } }
public class ThreadC extends Thread { private ThreadB threadB; public ThreadC(ThreadB threadB) { super(); this.threadB = threadB; } @Override public void run() { threadB.bService(); } }
-
測試類
public class Run { public static void main(String[] args) { try { ThreadB b = new ThreadB(); ThreadA a = new ThreadA(b); a.start(); Thread.sleep(1000); ThreadC c = new ThreadC(b); c.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }
執行結果:
b run begin timer=1607788792039 b run end timer=1607788797040 列印 b Service timer=1607788798039
由於執行緒 ThreadA 使用 Thread.sleep(long) 方法一直持有 ThreadB 物件的鎖,時間達到 6 秒,所以執行緒 ThreadC 只有在 ThreadA 時間到達 6 秒後釋放 ThreadB 的鎖時,才可以呼叫 ThreadB 中的同步方法 synchronized public void bService()。
下面繼續實驗,驗證 join() 方法釋放鎖的特點。
-
更改 ThreadA.java 類程式碼如下:
public class ThreadA extends Thread { private ThreadB b; public ThreadA(ThreadB b) { super(); this.b = b; } @Override public void run() { try { synchronized (b) { b.start(); b.join();// 說明join釋放鎖了 for (int i = 0; i < Integer.MAX_VALUE; i++) { String newString = new String(); Math.random(); } } } catch (InterruptedException e) { e.printStackTrace(); } } }
-
再次執行,結果如下
b run begin timer=1607790117470 列印 b Service timer=1607790118472 b run end timer=1607790122472
由於執行緒 ThreadA 釋放了 ThreadB 的鎖,所以執行緒 ThreadC 可以呼叫 ThreadB 中的同步方法 synchronized public void bService()。
此實驗也再次說明 join(long) 方法具有釋放鎖的特點。
3.2.6 方法 join() 後面的程式碼提前執行:出現意外
針對前面章節中的程式碼進行測試的過程中,還可以延伸出 “陷阱式” 的結果,如果稍加不注意,就會掉進 “陷阱” 裡。
示例如下:
-
建立兩個自定義的執行緒類
public class ThreadA extends Thread { private ThreadB b; public ThreadA(ThreadB b) { super(); this.b = b; } @Override public void run() { try { synchronized (b) { System.out.println("begin A ThreadName=" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(5000); System.out.println(" end A ThreadName=" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } } }
public class ThreadB extends Thread { @Override synchronized public void run() { try { System.out.println("begin B ThreadName=" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); Thread.sleep(5000); System.out.println(" end B ThreadName=" + Thread.currentThread().getName() + " " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
測試類
public class Run1 { public static void main(String[] args) { try { ThreadB b = new ThreadB(); ThreadA a = new ThreadA(b); a.start(); b.start(); b.join(2000); System.out.println(" main end " + System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } }
程式執行後,在控制檯列印結果有以下兩種情況:
begin A ThreadName=Thread-1 1607792596530
end A ThreadName=Thread-1 1607792601532
main end 1607792601532
begin B ThreadName=Thread-0 1607792601532
end B ThreadName=Thread-0 1607792606532
begin A ThreadName=Thread-1 1607793705864
end A ThreadName=Thread-1 1607793710866
begin B ThreadName=Thread-0 1607793710866
end B ThreadName=Thread-0 1607793715866
main end 1607793715866
為什麼出現截然不同的執行結果呢?
3.2.7 方法 join() 後面的程式碼提前執行:解釋意外
為了檢視 join() 方法在 Run1.java 類中執行的時機,建立 RunFirst.java 類檔案,程式碼如下:
public class RunFirst {
public static void main(String[] args) {
ThreadB b = new ThreadB();
ThreadA a = new ThreadA(b);
a.start();
b.start();
System.out.println(" main end=" + System.currentTimeMillis());
}
}
程式多次執行結果,如下所示:
main end=1607794763236
begin A ThreadName=Thread-1 1607794763236
end A ThreadName=Thread-1 1607794768237
begin B ThreadName=Thread-0 1607794768237
end B ThreadName=Thread-0 1607794773238
通過多次執行 RunFirst.java 檔案後,可以發現一個規律:main end 往往都是第一個列印的。所以可以完全確定地得出一個結論:方法 join(2000) 大部分是先執行的,也就是先搶到 ThreadB 的鎖,然後快速進行釋放。
而執行 Run1.java 檔案後就會出現一些不同的執行結果:
相關文章
- 《Java 多執行緒程式設計核心技術》筆記——第3章 執行緒間通訊(四)Java執行緒程式設計筆記
- java核心技術筆記--執行緒Java筆記執行緒
- java多執行緒5:執行緒間的通訊Java執行緒
- Java多執行緒-執行緒通訊Java執行緒
- java多執行緒間的通訊Java執行緒
- Java多執行緒學習——執行緒通訊Java執行緒
- Java併發程式設計之執行緒安全、執行緒通訊Java程式設計執行緒
- 多執行緒之間通訊及執行緒池執行緒
- Java多執行緒程式設計筆記9:ReentrantReadWriteLockJava執行緒程式設計筆記
- Java核心技術學習筆記——進階——第五章 Java多執行緒和併發程式設計——5.2 Java多執行緒實現Java筆記執行緒程式設計
- 多執行緒核心技術(1)-執行緒的基本方法執行緒
- Java多執行緒學習(3)執行緒同步與執行緒通訊Java執行緒
- 多執行緒筆記 三執行緒筆記
- 【Java】【多執行緒】兩個執行緒間的通訊、wait、notify、notifyAllJava執行緒AI
- Java多執行緒筆記Java執行緒筆記
- java多執行緒:執行緒間通訊——生產者消費者模型Java執行緒模型
- Android小知識-Java多執行緒相關(執行緒間通訊)上篇AndroidJava執行緒
- Swift多執行緒:使用Thread進行多執行緒間通訊,協調子執行緒任務Swift執行緒thread
- Java多執行緒程式設計筆記2:synchronized同步方法Java執行緒程式設計筆記synchronized
- Java多執行緒程式設計筆記10:單例模式Java執行緒程式設計筆記單例模式
- 程式間通訊(linux程式與執行緒學習筆記)Linux執行緒筆記
- Java多執行緒學習(五)執行緒間通訊知識點補充Java執行緒
- 多執行緒之間的通訊執行緒
- [短文速讀 -5] 多執行緒程式設計引子:程式、執行緒、執行緒安全執行緒程式設計
- 說說Java執行緒間通訊Java執行緒
- 多執行緒,執行緒類三種方式,執行緒排程,執行緒同步,死鎖,執行緒間的通訊,阻塞佇列,wait和sleep區別?執行緒佇列AI
- 多執行緒筆記執行緒筆記
- Java執行緒通訊Java執行緒
- 多執行緒程式設計的核心思想執行緒程式設計
- Java多執行緒-執行緒中止Java執行緒
- Java 多執行緒學習筆記Java執行緒筆記
- Android執行緒間通訊Android執行緒
- 多執行緒------執行緒與程式/執行緒排程/建立執行緒執行緒
- java多執行緒程式設計:你真的瞭解執行緒中斷嗎?Java執行緒程式設計
- 多執行緒程式設計基礎(一)-- 執行緒的使用執行緒程式設計
- Java併發程式設計之執行緒篇之執行緒中斷(三)Java程式設計執行緒
- 多執行緒Demo學習(執行緒的同步,簡單的執行緒通訊)執行緒
- python 多執行緒程式設計Python執行緒程式設計