線程通信的方式 要想實現線程之間的協同, 如: 線程先後執行順序, 獲取某個線程的執行結果等, 涉及線程之間的相互通信, 分為下麵四類 文件共用 網路共用 變數共用 JDK提供的線程協調API 細分為: suspend/resume, wait/notify, park/unpark 文件共用 變數 ...
線程通信的方式
要想實現線程之間的協同, 如: 線程先後執行順序, 獲取某個線程的執行結果等, 涉及線程之間的相互通信, 分為下麵四類
- 文件共用
- 網路共用
- 變數共用
- JDK提供的線程協調API 細分為:
suspend/resume, wait/notify, park/unpark
文件共用
變數共用
線程協作 - JDK API
典型場景: 生產者 - 消費者模型 (線程阻塞, 線程喚醒)
示例: 線程1區買包子 , 沒有包子, 則不執行。 線程2生產包子, 通知線程1繼續執行
API - 被棄用的suspend和resume
調用suspend掛起目標線程, 通過resume可以恢複線程執行, 對調用順序有要求,也要開發者自己註意鎖的釋放。這個被棄用的API, 容易死鎖,也容易導致永久掛起。
代碼示例:
/** 正常的suspend/resume */
public void suspendResumeTest() throws Exception {
// 啟動線程
Thread consumerThread =
new Thread(
() -> {
if (baozidian == null) { // 如果沒包子,則進入等待
System.out.println("1、進入等待");
Thread.currentThread().suspend();
}
System.out.println("2、買到包子,回家");
});
consumerThread.start();
// 3秒之後,生產一個包子
Thread.sleep(3000L);
baozidian = new Object();
consumerThread.resume();
System.out.println("3、通知消費者");
}
/** 死鎖的suspend/resume。 suspend並不會像wait一樣釋放鎖,故此容易寫出死鎖代碼 */
public void suspendResumeDeadLockTest() throws Exception {
// 啟動線程
Thread consumerThread =
new Thread(
() -> {
if (baozidian == null) { // 如果沒包子,則進入等待
System.out.println("1、進入等待");
// 當前線程拿到鎖,然後掛起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("2、買到包子,回家");
});
consumerThread.start();
// 3秒之後,生產一個包子
Thread.sleep(3000L);
baozidian = new Object();
// 爭取到鎖以後,再恢復consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println("3、通知消費者");
}
/** 先執行resume再執行suspend導致程式永久掛起的suspend/resume */
public void suspendResumeDeadLockTest2() throws Exception {
// 啟動線程
Thread consumerThread =
new Thread(
() -> {
if (baozidian == null) {
System.out.println("1、沒包子,進入等待");
try { // 為這個線程加上一點延時
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 這裡的掛起執行在resume後面
Thread.currentThread().suspend();
}
System.out.println("2、買到包子,回家");
});
consumerThread.start();
// 3秒之後,生產一個包子
Thread.sleep(3000L);
baozidian = new Object();
consumerThread.resume();
System.out.println("3、通知消費者");
consumerThread.join();
}
wait/notify機制
這些方法只能由同一對象鎖的線程持有者調用,也就是寫在同步代碼塊裡面, 否則會拋出IllegalMonitorStateException異常。
wait方法導致當前線程等待, 加入該對象的等待集合中, 並且放棄當前持有的對象鎖
notify/notifyAll喚醒一個/所有正在等待這個對象鎖的線程
註意: 雖然wait會自動解鎖, 但對順序有要求, 如果在notify被調用之後, 才開始wait方法的調用, 線程會永遠處於WAINTING狀態
代碼示例:
/** 正常的wait/notify */
public void waitNotifyTest() throws Exception {
// 啟動線程
new Thread(
() -> {
if (baozidian == null) { // 如果沒包子,則進入等待
synchronized (this) {
try {
System.out.println("1、進入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、買到包子,回家");
})
.start();
// 3秒之後,生產一個包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消費者");
}
}
/** 會導致程式永久等待的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("1、進入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、買到包子,回家");
})
.start();
// 3秒之後,生產一個包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消費者");
}
}
park/unpark機制
線程調用park則等待“許可”, unpark方法為指定線程提供“許可”。 不要求park和unpark方法的調用順序。 多次調用unpark後再調用park, 線程會直接運行, 但不會疊加, 也就是說, 連續多次調用park方法, 第一次會拿到“許可”直接運行, 後續調用會進入等待。
註意: park/unpark 對調用順序沒有要求, 但是並不會釋放鎖
代碼示例:
/** 正常的park/unpark */
public void parkUnparkTest() throws Exception {
// 啟動線程
Thread consumerThread =
new Thread(
() -> {
if (baozidian == null) { // 如果沒包子,則進入等待
System.out.println("1、進入等待");
LockSupport.park();
}
System.out.println("2、買到包子,回家");
});
consumerThread.start();
// 3秒之後,生產一個包子
Thread.sleep(3000L);
baozidian = new Object();
LockSupport.unpark(consumerThread);
System.out.println("3、通知消費者");
}
/** 死鎖的park/unpark */
public void parkUnparkDeadLockTest() throws Exception {
// 啟動線程
Thread consumerThread =
new Thread(
() -> {
if (baozidian == null) { // 如果沒包子,則進入等待
System.out.println("1、進入等待");
// 當前線程拿到鎖,然後掛起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("2、買到包子,回家");
});
consumerThread.start();
// 3秒之後,生產一個包子
Thread.sleep(3000L);
baozidian = new Object();
// 爭取到鎖以後,再恢復consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("3、通知消費者");
}
偽喚醒
之前代碼中用if語句來判斷是否進入等待是錯誤的
官方建議應該在迴圈中檢查條件,原因是處於等待狀態的線程可能會收到錯誤警報和偽喚醒, 如果不在迴圈中檢查等待條件, 程式就會在沒有滿足結束條件的情況下退出
偽喚醒 :指線程並非因為notify, notifyAll, unpark等API調用而喚醒, 是更底層的原因導致的。
-
本人剛學先上鏈接(別人寫的挺好的)後期同步補上😄!!! 網站鏈接:https://blog.csdn.net/baozhiqiangjava/article/details/81178694 GitHub:https://github.com/JsAaron/jQuery ...
-
SpringCloud系列教程 | 第十二篇:Spring Cloud Gateway初探 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如無特殊說明,本系列教程全採用以上版本 前面我們在聊服務網關Zuul的時候提到了Gateway,那麼Z ...
-
"JavaScript 設計模式基礎(一)" "小菜鳥的個人博客" 原型模式 在以類為中心的面向對象編程語言中,類和對象的關係就像鑄模和鑄件的關係,對象總是從類中創建。而原型編程中,類不是必須的,對象未必從類中創建而來,可以拷貝另一個對象而變成新對象 從設計模式角度講,原型模式是用於創建對象的一種模 ...
-
相比較傳統的工廠模式IFactory/Concrete Factory會反覆引用並編譯代碼 但是作為開發人員,我們更希望的是少修改代碼,儘量從配置著手也就是設計模式的根本原則之一:開放封閉原則。如果我要增加新的產品,那麼修改就比較大了,對於業務來講還是可以接受的。但是如果可以做到不修改代碼是最好的。 ...
-
架構雜談《二》 服務化到微服務 1、微服務的產生 隨著互聯網企業的不斷發展,海量用戶發起的大規模、高併發請求是企業不得不面對的,上一篇 架構雜談《一》雜談的SOA服務化系統能夠分解任務,讓每個服務更簡單、職責單一、更易於擴展。但無論是Web Service 還是ESB,都有時代遺留下的問題。 Web ...
-
一些討論 1. [Python中使用配置文件的最佳實踐][1] 2. [Python中使用配置文件的最好方法][2] 3. [Python符號常量][3] 4. [多種配置文件方案對比][4] [1]: %20%E5%8F%82%E8%A7%81SO%E7%9A%84%E8%AE%A8%E8%AE% ...
-
1.代碼生成器: [正反雙向](單表、主表、明細表、樹形表,快速開發利器)freemaker模版技術 ,0個代碼不用寫,生成完整的一個模塊,帶頁面、建表sql腳本、處理類、service等完整模塊2.多數據源:(支持同時連接無數個資料庫,可以不同的模塊連接不同數的據庫)支持N個數據源3.阿裡資料庫連 ...
-
本文為本次系列文章的第一篇,接下來,小編預計用一周的時間,帶大家重新解讀二十三中設計模式。 ...