線程通信的方式
要想實現(xiàn)線程之間的協(xié)同, 如: 線程先后執(zhí)行順序, 獲取某個線程的執(zhí)行結(jié)果等, 涉及線程之間的相互通信, 分為下面四類
- 文件共享
- 網(wǎng)絡(luò)共享
- 變量共享
- JDK提供的線程協(xié)調(diào)API 細(xì)分為:
suspend/resume, wait/notify, park/unpark
文件共享

變量共享

線程協(xié)作 - JDK API
典型場景: 生產(chǎn)者 - 消費者模型 (線程阻塞, 線程喚醒)
示例: 線程1區(qū)買包子 , 沒有包子, 則不執(zhí)行。 線程2生產(chǎn)包子, 通知線程1繼續(xù)執(zhí)行

API - 被棄用的suspend和resume
調(diào)用suspend掛起目標(biāo)線程, 通過resume可以恢復(fù)線程執(zhí)行, 對調(diào)用順序有要求,也要開發(fā)者自己注意鎖的釋放。這個被棄用的API, 容易死鎖,也容易導(dǎo)致永久掛起。
代碼示例:
/** 正常的suspend/resume */
public void suspendResumeTest() throws Exception {
// 啟動線程
Thread consumerThread =
new Thread(
() -> {
if (baozidian == null) { // 如果沒包子,則進(jìn)入等待
System.out.println("1、進(jìn)入等待");
Thread.currentThread().suspend();
}
System.out.println("2、買到包子,回家");
});
consumerThread.start();
// 3秒之后,生產(chǎn)一個包子
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) { // 如果沒包子,則進(jìn)入等待
System.out.println("1、進(jìn)入等待");
// 當(dāng)前線程拿到鎖,然后掛起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("2、買到包子,回家");
});
consumerThread.start();
// 3秒之后,生產(chǎn)一個包子
Thread.sleep(3000L);
baozidian = new Object();
// 爭取到鎖以后,再恢復(fù)consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println("3、通知消費者");
}
/** 先執(zhí)行resume再執(zhí)行suspend導(dǎo)致程序永久掛起的suspend/resume */
public void suspendResumeDeadLockTest2() throws Exception {
// 啟動線程
Thread consumerThread =
new Thread(
() -> {
if (baozidian == null) {
System.out.println("1、沒包子,進(jìn)入等待");
try { // 為這個線程加上一點延時
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 這里的掛起執(zhí)行在resume后面
Thread.currentThread().suspend();
}
System.out.println("2、買到包子,回家");
});
consumerThread.start();
// 3秒之后,生產(chǎn)一個包子
Thread.sleep(3000L);
baozidian = new Object();
consumerThread.resume();
System.out.println("3、通知消費者");
consumerThread.join();
}
wait/notify機(jī)制
這些方法只能由同一對象鎖的線程持有者調(diào)用,也就是寫在同步代碼塊里面, 否則會拋出IllegalMonitorStateException異常。
wait方法導(dǎo)致當(dāng)前線程等待, 加入該對象的等待集合中, 并且放棄當(dāng)前持有的對象鎖
notify/notifyAll喚醒一個/所有正在等待這個對象鎖的線程
注意: 雖然wait會自動解鎖, 但對順序有要求, 如果在notify被調(diào)用之后, 才開始wait方法的調(diào)用, 線程會永遠(yuǎn)處于WAINTING狀態(tài)
代碼示例:
/** 正常的wait/notify */
public void waitNotifyTest() throws Exception {
// 啟動線程
new Thread(
() -> {
if (baozidian == null) { // 如果沒包子,則進(jìn)入等待
synchronized (this) {
try {
System.out.println("1、進(jìn)入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、買到包子,回家");
})
.start();
// 3秒之后,生產(chǎn)一個包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消費者");
}
}
/** 會導(dǎo)致程序永久等待的wait/notify */
public void waitNotifyDeadLockTest() throws Exception {
// 啟動線程
new Thread(
() -> {
if (baozidian == null) { // 如果沒包子,則進(jìn)入等待
try {
Thread.sleep(5000L);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (this) {
try {
System.out.println("1、進(jìn)入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、買到包子,回家");
})
.start();
// 3秒之后,生產(chǎn)一個包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消費者");
}
}
park/unpark機(jī)制
線程調(diào)用park則等待“許可”, unpark方法為指定線程提供“許可”。 不要求park和unpark方法的調(diào)用順序。 多次調(diào)用unpark后再調(diào)用park, 線程會直接運行, 但不會疊加, 也就是說, 連續(xù)多次調(diào)用park方法, 第一次會拿到“許可”直接運行, 后續(xù)調(diào)用會進(jìn)入等待。
注意: park/unpark 對調(diào)用順序沒有要求, 但是并不會釋放鎖
代碼示例:
/** 正常的park/unpark */
public void parkUnparkTest() throws Exception {
// 啟動線程
Thread consumerThread =
new Thread(
() -> {
if (baozidian == null) { // 如果沒包子,則進(jìn)入等待
System.out.println("1、進(jìn)入等待");
LockSupport.park();
}
System.out.println("2、買到包子,回家");
});
consumerThread.start();
// 3秒之后,生產(chǎn)一個包子
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) { // 如果沒包子,則進(jìn)入等待
System.out.println("1、進(jìn)入等待");
// 當(dāng)前線程拿到鎖,然后掛起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("2、買到包子,回家");
});
consumerThread.start();
// 3秒之后,生產(chǎn)一個包子
Thread.sleep(3000L);
baozidian = new Object();
// 爭取到鎖以后,再恢復(fù)consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("3、通知消費者");
}
偽喚醒
之前代碼中用if語句來判斷是否進(jìn)入等待是錯誤的
官方建議應(yīng)該在循環(huán)中檢查條件,原因是處于等待狀態(tài)的線程可能會收到錯誤警報和偽喚醒, 如果不在循環(huán)中檢查等待條件, 程序就會在沒有滿足結(jié)束條件的情況下退出
偽喚醒 :指線程并非因為notify, notifyAll, unpark等API調(diào)用而喚醒, 是更底層的原因?qū)е碌摹?
?