寫在前面:
視訊是什麼東西,有看文檔精彩嗎?
視訊是什麼東西,有看文檔速度快嗎?
視訊是什麼東西,有看文檔效率高嗎?
諸小亮:下面我們聊聊——線程通信技術
張小飛:多個線程之間還可以通信嗎?
諸小亮:是的,通過調用鎖對象的wait()、notify()方法,可以實作線程通信
張小飛:那具體如何使用呢?
1. 體驗
諸小亮:首先,Object 類中有 wait()、notify() 這兩個方法
- wait():讓目前線程進入等待狀态
- 使用方式:鎖對象.wait(),必須放到 同步代碼塊 或 同步方法 中
- notify():喚醒對應的正在 wait 的線程
- 使用方式:鎖對象.notify(),必須放到 同步代碼塊 或 同步方法 中
- 調用 wait() 和 notify() 的鎖對象必須是同一個
張小飛:大概明白,不過還需要您給出具體的代碼
諸小亮:代碼也給你準備好了,注意代碼中的注釋
class Hero implements Runnable{
//1. 搞一個鎖對象
public static Object lock = new Object();
@Override
public void run(){
synchronized (lock){
System.out.println(Thread.currentThread().getName() + "。。。。。。。擷取鎖,然後進入等待狀态");
try {
//2. 讓目前線程等待,使用方式:鎖對象.wait(),而且必須放到同步代碼塊或者同步方法中
lock.wait();
} catch (InterruptedException e) { }
System.out.println(Thread.currentThread().getName() + "。。。。。。。結束");
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
//3. 建立一個 yase 線程
Thread yase = new Thread(new Hero(),"yase");
yase.start();
//4. 主線程休眠2秒
Thread.sleep(2000);
//5. 使用notify喚醒yase線程,讓他繼續執行
synchronized (Hero.lock){
System.out.println("主線程喚醒yase。。。。。。。");
//使用方式:鎖對象.notify(),喚醒對應的正在等待狀态的線程,必須放到同步代碼塊或者同步方法中
Hero.lock.notify();
}
}
}
結果:
諸小亮:你能解釋一下這個代碼的執行過程嗎?
張小飛:當然可以
yase線程先執行,在執行 lock.wait() 代碼後,進入等待狀态
main 線程在休眠 2 秒後,執行 Hero.lock.notify(); 去喚醒 yase 線程
yase線程被喚醒後,接着執行,最後輸出:yase。。。。。。。結束
諸小亮:不錯啊
2. 注意點
諸小亮:使用 wait、notify時,需要特别注意一些地方
張小飛:都需要注意什麼?
1. wait、notify必須放到同步代碼塊或同步方法中
諸小亮:第一,wait、notify必須放到同步代碼塊或同步方法中
張小飛:哦~,這個剛才說過,不過如果不放到它裡面呢?
諸小亮:那調用 wait 方法時就會報錯,比如:
結果:
張小飛:明白了,不過這個異常是?
諸小亮:非法的監控狀态異常,線程從啟動到死亡有不同的狀态,之後會解釋
張小飛:好的
2. 鎖對象是this
張小飛:如果鎖對象是 this 呢?
諸小亮:如果鎖對象是this,那麼:this.wait(),記住必須是:鎖對象.wait(),否則:
上圖,鎖對象是this,但是卻調用 lock.wait();,結果:
張小飛:記住了
3. 鎖對象.notify()
張小飛:既然必須是 鎖對象.wait(),那麼也肯定必須是:鎖對象.notify()
諸小亮:沒錯,否則也會報錯,比如:
上圖,鎖對象是Hero.class,但是卻調用Hero.lock.nodify();結果:
4. 調用 wait() 和 notify() 的鎖對象必須是同一個
諸小亮:還有,調用 wait() 和 notify() 的鎖對象必須是同一個,否則:
結果:
張小飛:看輸出,程式一直沒有結束吧
諸小亮:是的,其原因
- Hero.class.notify();隻能喚醒鎖對象是Hero.class 且 執行了 Hero.class.wait() 的線程
- yase線程的鎖對象是lock,執行了lock.wait(),一直在等待被人喚醒,是以程式一直不結束
5. 執行 notify 後,正在 wait 的線程并不是立即運作
張小飛:我發現一個現象
諸小亮:什麼現象?
張小飛:執行 notify 後,正在 wait 的線程并不是立即運作,需要等待執行 notify 的線程釋放鎖
結果:
諸小亮:嗯,你說的很對,這也是一個需要關注的地方
6. wait()等待時,會釋放鎖對象
張小飛:還有一個問題,執行 wait() 方法後,是不是就釋放鎖對象了?
諸小亮:不錯,隻有釋放了鎖對象,main線程才能進入 synchronized 代碼中
上圖可以看出,yase先執行,如果wait()不釋放鎖,那麼就無法執行Hero.lock.notify()這句代碼
7. 多個線程都在wait(),notify隻會随機的喚醒一個
張小飛:如果有多個線程都調用了 wait() 方法呢?
諸小亮:問得好,不過,你何不自己試一試呢?
張小飛:嗯嗯,好的
public static void main(String[] args) throws Exception {
//建立 2 個線程
Thread yase = new Thread(new Hero(),"yase");
yase.start();
Thread laoyase = new Thread(new Hero(),"laoyase");
laoyase.start();
Thread.sleep(2000);
//使用方式:鎖對象.notify(),喚醒對應的正在等待狀态的線程,必須放到同步代碼塊或者同步方法中
synchronized (Hero.lock){
System.out.println("主線程喚醒。。。。。。。");
Hero.lock.notify();//有多個線程都在wait(),notify隻會随機的喚醒一個
}
}
結果:
張小飛:結果出來了,yase線程結束了,但是 laoyase 這個線程還在阻塞
諸小亮:其實你多執行幾次,會發現——多個線程都在wait(),notify隻會随機的喚醒一個
張小飛:好的,我再試試
8. notifyAll()
張小飛:有沒有可以把所有都在 wait 中的線程,都喚醒呢?
諸小亮:如果想全部喚醒,可以使用notifyAll(),或者執行多次notify(),比如:
結果:
另外,執行多次notify()方法也可以,全部喚醒,比如:
結果:略
9. 特殊情況下的notify
諸小亮:特殊情況下的 notify 方法會喚醒所有
張小飛:這是什麼意思?
諸小亮:來,看代碼,注意代碼中的注釋
class Hero extends Thread{
public void run(){
//1. 注意這裡的鎖對象是this
synchronized (this){
//2. 執行 notify 喚醒所有
this.notify();
System.out.println("lock線程運作----"+this.getName());
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
//3. 建立一個Thread對象,作為鎖對象
Hero yase = new Hero();
yase.setName("yase");
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程daji開始運作。。。。。。");
//4. 注意,這裡的鎖對象是一個線程對象
synchronized (yase){
try {
yase.wait();
System.out.println("線程daji結束----"+yase.getName());//輸出lock線程的名稱
} catch (InterruptedException e) {
}
}
}
}, "daji").start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程lvbu開始運作。。。。。。");
//4. 注意,這裡的鎖對象是一個線程對象
synchronized (yase){
try {
yase.wait();
System.out.println("線程vbul結束----"+yase.getName());
} catch (InterruptedException e) {
}
}
}
}, "lvbu").start();
Thread.sleep(2000);
//5. 啟動 yase 線程
yase.start();
}
}
結果:
張小飛:這個代碼能否具體解釋一下?
諸小亮:當然可以
首先建立一個名為 yase 的線程對象
然後建立了 daji、lvbu 兩個線程,這兩個線程都把 yase 對象作為鎖對象
這兩個線程分别啟動後都執行 wait 方法,進入等待模式
最後yase線程啟動,執行run方法時,調用 this.notify();
結果:daji、lvbu 都被喚醒
張小飛:這是什麼原理?
諸小亮:額。。。。,很抱歉我也沒仔細研究過,暫時記住這個現象吧
張小飛:好的
3. 生産消費模式
諸小亮:接着我們說一個——生産消費模式
張小飛:這是做什麼的?
諸小亮:生産者生産資料,消費者消費資料,先看一張圖
張小飛:您是先說,面包師生産面包,放到面包櫃,然後銷售員再取走面包嗎?
諸小亮:是的,這就是我們生活中的——生産消費模式
諸小亮:工作中,wait()、notify() 經常用于生産消費模式
張小飛:具體怎麼使用呢?
諸小亮:給你一個需求,生産者生産商品,如果貨架上已經有商品就不再生産,
消費者消費商品,如果貨架上沒有就通知生産者
示例:
class Goods{
String name = "哈根達斯";
//0:表示貨架上沒有商品,需要生産
//1:表示貨架上已有商品,需要消費
int count = 0;
}
//消費者
class Consumer implements Runnable{
private Goods goods;
Consumer(Goods goods){
this.goods = goods;
}
@Override
public void run(){
while (true){
synchronized (goods){
if(goods.count == 1){
System.out.println(Thread.currentThread().getName()+"。。。。。。消費商品---"+goods.count);
goods.count = 0;//消費商品
goods.notify();//喚醒消費者
//讓自己自己等待
try {goods.wait();} catch (InterruptedException e) {}
}
}
}
}
}
//生産者
class Producer implements Runnable{
private Goods goods;
Producer(Goods goods){
this.goods = goods;
}
@Override
public void run(){
while (true){
synchronized (goods){
if(goods.count == 0){
System.out.println(Thread.currentThread().getName()+"。。。。。。生産商品---------"+goods.count);
goods.count = 1;//生産商品,設定count=1
goods.notify();//喚醒消費者
//自己等待
try {goods.wait();} catch (InterruptedException e) {}
}
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws Exception {
Goods goods = new Goods();
new Thread(new Producer(goods), "伊利").start();
new Thread(new Consumer(goods), "亞瑟").start();
}
}
結果中,兩個線程互相喚醒,交替執行
諸小亮:怎麼樣,可以看懂嗎?
張小飛:可以可以,原來這就是生産消費模式
4. wait 和 sleep
張小飛:我突然先到,sleep 也是可以暫停線程的
諸小亮:不錯, wait 和 sleep 有相同點,也有差別,這也是面試常見的一個問題
張小飛:那麼它們都有什麼不同的地方呢?
諸小亮:都給你總結好了,自己看看吧
相同點:都可以暫時停止一個線程
不同點:
sleep必須指定睡眠時間,wait可以指定也可以不指定
sleep是 Thread 的靜态方法,wait是 Object 類的成員方法
sleep 不釋放鎖,wait 釋放鎖
sleep 等待一定時間後自定運作,wait需要 notify 或 notifyAll 喚醒
wait 必須配合synchronized使用,sleep不必
sleep 會讓線程進入 TIMED_WAITING 狀态,wait讓線程進入 WAITING 狀态(之後會詳細解釋線程狀态)