線程通訊的三種方法!通俗易懂
線程通信是指多個(gè)線程之間通過(guò)某種機(jī)制進(jìn)行協(xié)調(diào)和交互,例如,線程等待和通知機(jī)制就是線程通訊的主要手段之一。
在 Java 中,線程等待和通知的實(shí)現(xiàn)手段有以下幾種方式:
- Object 類(lèi)下的 wait()、notify() 和 notifyAll() 方法;
- Condition 類(lèi)下的 await()、signal() 和 signalAll() 方法;
- LockSupport 類(lèi)下的 park() 和 unpark() 方法。
為什么一個(gè)線程等待和通知機(jī)制就需要這么多的實(shí)現(xiàn)方式呢?
別著急,咱們先來(lái)看實(shí)現(xiàn),再來(lái)說(shuō)原因。
一、wait/notify/notifyAll
Object 類(lèi)的方法說(shuō)明:
- wait():讓當(dāng)前線程處于等待狀態(tài),并釋放當(dāng)前擁有的鎖;
- notify():隨機(jī)喚醒等待該鎖的其他線程,重新獲取鎖,并執(zhí)行后續(xù)的流程,只能喚醒一個(gè)線程;
- notifyAll():?jiǎn)拘阉械却撴i的線程(鎖只有一把,雖然所有線程被喚醒,但所有線程需要排隊(duì)執(zhí)行)。
示例代碼如下:
Object lock = new Object();
// 創(chuàng)建線程并執(zhí)行
new Thread(() -> {
System.out.println("線程1:開(kāi)始執(zhí)行");
synchronized (lock) {
try {
System.out.println("線程1:進(jìn)入等待");
lock.wait();
System.out.println("線程1:繼續(xù)執(zhí)行");
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("線程1:執(zhí)行完成");
}
}).start();
Thread.sleep(1000);
synchronized (lock) {
// 喚醒線程
System.out.println("執(zhí)行 notifyAll()");
lock.notifyAll();
}
二、await/signal/signalAll
Condition 類(lèi)的方法說(shuō)明:
- await():對(duì)應(yīng) Object 的 wait() 方法,線程等待;
- signal():對(duì)應(yīng) Object 的 notify() 方法,隨機(jī)喚醒一個(gè)線程;
- signalAll():對(duì)應(yīng) Object 的 notifyAll() 方法,喚醒所有線程。
示例代碼如下:
// 創(chuàng)建 Condition 對(duì)象
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition(); // lock 下可創(chuàng)建多個(gè) Condition
// 加鎖
lock.lock();
try {
// 業(yè)務(wù)方法......
// 1.進(jìn)入等待狀態(tài)
condition.await();
// 2.喚醒操作
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
三、park/unpark
LockSupport 類(lèi)的方法說(shuō)明:
- LockSupport.park():休眠當(dāng)前線程。
- LockSupport.unpark(線程對(duì)象):?jiǎn)拘涯骋粋€(gè)指定的線程。
PS:LockSupport 無(wú)需配鎖(synchronized 或 Lock)一起使用。
示例代碼如下:
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
LockSupport.park();
System.out.println("線程1");
}, "線程1");
t1.start();
Thread t2 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("喚醒線程1");
LockSupport.unpark(t1);
}, "線程2");
t2.start();
}
四、小結(jié)
為什么一個(gè)線程等待和喚醒的功能需要這么多的實(shí)現(xiàn)呢?
- LockSupport 存在的必要性:前兩種方法 notify 方法以及 signal 方法都是隨機(jī)喚醒,如果存在多個(gè)等待線程的話,可能會(huì)喚醒不應(yīng)該喚醒的線程,因此有 LockSupport 類(lèi)下的 park 和 unpark 方法指定喚醒線程是非常有必要的。
- Condition 存在的必要性:Condition 相比于 Object 類(lèi)的 wait 和 notify/notifyAll 方法,前者可以創(chuàng)建多個(gè)等待集,例如,我們可以創(chuàng)建一個(gè)生產(chǎn)者等待喚醒對(duì)象,和一個(gè)消費(fèi)者等待喚醒對(duì)象,這樣我們就能實(shí)現(xiàn)生產(chǎn)者只能喚醒消費(fèi)者,而消費(fèi)者只能喚醒生產(chǎn)者的業(yè)務(wù)邏輯了,如下代碼所示:
// 創(chuàng)建 Condition 對(duì)象
private Lock lock = new ReentrantLock();
// 生產(chǎn)者的 Condition 對(duì)象
private Condition producerCondition = lock.newCondition();
// 本篇內(nèi)容出自磊哥《Java面試突擊訓(xùn)練營(yíng)》 VX:GG_Stone
// 消費(fèi)者的 Condition 對(duì)象
private Condition consumerCondition = lock.newCondition();
也就是 Condition 是 Object 等待喚醒模型的升級(jí),Object 類(lèi)可以實(shí)現(xiàn)的功能它都能實(shí)現(xiàn),但 Condition 能實(shí)現(xiàn)的功能,Object 卻不能實(shí)現(xiàn),這就是 Condition 類(lèi)存在的必要性。
那問(wèn)題來(lái)了,為什么還有會(huì) Object 的 wait 和 notify 方法呢?因?yàn)?Object 類(lèi)誕生的比較早,也就是說(shuō) Condition 和 LockSupport 都是 JDK 后期版本才出現(xiàn)的功能,所以就有了現(xiàn)在這么多線程喚醒和等待的方法了。