(目錄)
對象等待集來幹預線程的排程
wait 和 notify 為了處理線程排程随機性的問題。
還是那句話,多線程的排程,因為它的随機性,就導緻代碼誰先執行,誰後執行,存在太多的變數。
而我們程式員是不喜歡随機性,我們喜歡确定的東西。
需要能夠讓線程彼此之間,有一個固定的順序。
舉個例子:打籃球
籃球裡面有一個典型的操作:傳球,上籃。
那麼我們肯定得先傳球,再上籃。需要兩個隊員的互相配合。
兩個隊員也就是兩個線程。
如果是先做個上籃動作,但是球沒到,也就上了個寂寞。
一般最穩的方法,都是先傳球再上籃。
像這樣的順序,在我們實際開發中也是非常需要的。
是以我們就需要有手段去控制!
之前講到的 join 也是一種控制順序的方式,但是
join更傾向于控制線程結束
。是以
join 是有使用的局限性。
就不像 wait 和 notify 用起來更加合适。
wait 方法
其實wait()方法就是使線程停止運作。
-
,wait( )方法是Object類的方法,該方法是用來将目前線程置入“預執行隊列”中,并且在wait()所在的代碼處停止執行,直到接到通知或被中斷為止。方法wait()的作用是使目前執行代碼的線程進行等待
-
。wait( )方法隻能在同步方法中或同步塊中調用。如果調用wait()時,沒有持有适當的鎖,會抛出異常
- wait( )方法執行後,目前線程釋放鎖,并一直處于等待通知狀态, 直到其他線程喚醒該線程後與其它線程競争重新擷取鎖。
調用 wait 方法的線程,就會陷入阻塞,阻塞到有其它線程通過 notify 來通知。
public class ThreadTest2 {
public static void main(String[] args) {
Object locker = new Object();
Thread t = new Thread() {
@Override
public void run() {
synchronized (locker) {
System.out.println("t 開始運作");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" t 運作結束");
}
}
};
t.start();
}
}
notify 方法
一個線程執行到object.wait()之後就一直等待下去,那麼程式肯定不能一直這麼等待下去了。這個時候就需要使用到了另外一個方法喚醒的方法notify()。
notify方法就是使停止的線程繼續運作
-
,該方法是用來通知那些可能等待該對象的對象鎖的其它線程,對其發出通知notify,并使它們重新擷取該對象的對象鎖。方法notify()也要在同步方法或同步塊中調用
如果有多個線程等待,則有線程規劃器随機挑選出一個呈wait狀态的線程。
- 在notify()方法後,目前線程不會馬上釋放該對象鎖,要等到執行notify()方法的線程将程式執行完,也就是退出同步代碼塊之後才會釋放對象鎖。
public class Test18 {
// 為了兩個線程能夠順利互動,我們建立一個鎖對象
private static Object locker = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
//進行 wait
synchronized (locker){
System.out.println("wait 之前");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait 之後");
}
});
t1.start();
// 為了大家能更清楚的觀察到現象,我這裡使用 sleep 延遲3s
Thread.sleep(3000);
Thread t2 = new Thread(()->{
// 進行 notify
synchronized (locker){
System.out.println("notify 之前");
locker.notify();
System.out.println("notify 之後");
}
});
t2.start();
}
}
根據這種機制我們可以編排多個線程的順序
notifyAll 方法
前面說的 wait 和 notify 都是針對同一個對象來操作。
例如:
現在有,被 10個線程調用了
一個對象 o
o.wait
。
此時
10 個 線程都是阻塞狀态
。
如果調用了
,就會把
o.notify
10個線程中的一個給喚醒
【随機喚醒:不确定下一個被喚醒的線程是哪一個】
被喚醒的線程就會繼續往下執行。其他線程仍處于阻塞狀态。
如果
,就會
調用的 o.notifyAll
把所有的線程全部喚醒
wait 在被喚醒之後,會重新嘗試擷取到鎖,這個過程就會發生競争
喚醒所有的線程,都去搶鎖。
搶到的,才可以繼續往下執行。
沒搶到的線程,繼續等待,等待下一次的 notifyAll。**
wait方法總結
的作用是
wait( )
,同時,wait( )也會讓目前線程釋放它所持有的鎖。
讓目前線程進入等待狀态
直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,使目前線程被喚醒(進入“就緒狀态”)
- notify()和notifyAll()的作用,則是喚醒目前對象上的等待線程
notify()是喚醒單個線程
, 然後這些線程再去競争那同一把鎖。(不建議使用notifyAll )
notifyAll()是喚醒所有的線程
”
wait(long timeout)讓目前線程處于“等待(阻塞)狀态
- “直到其他線程調用此對象的notify()方法或 notifyAll() 方法
- 或者超過指定的時間量”,目前線程被喚醒(進入“就緒狀态”)
wait() 的工作過程就是:
總結:
釋放對象鎖(是以必須有一個鎖才能正常執行)
(時間可能會長, 因為不參與後序鎖的競争直到有其他線程調用該對象的notify()去喚醒該線程)
等待通知
收到通知後 嘗試重新擷取對象鎖 繼續往下執行
特别注意
-
否則會報異常wait() 和 notify() 操作必須放在 synchronized 的代碼塊之内使用
-
更準确的說: 因為wait 和 notify 要在 synchronized 的代碼塊内 是以上鎖的對象也應該和 wait() 和 notify() 的對象必須是同一個對象 這樣就是4個對象得保持一緻調用wait() 和 notify() 的對象必須是同一個對象 才能起到等待和喚醒操作
notify方法總結
- 一個線程執行到object.wait()之後就一直等待下去,那麼程式肯定不能一直這麼等待下去了。這個時候就需要使用到了另外一個方法喚醒的方法notify()。
- notify方法就是使停止的線程繼續運作。
- 方法notify()也要在同步方法或同步塊中調用,該方法是用來通知那些可能等待該對象的對象鎖的其它線程,對其發出通知notify,并使它們重新擷取該對象的對象鎖。如果有多個線程等待,則有線程規劃器随機挑選出一個呈wait狀态的線程。
- 在notify()方法後,目前線程不會馬上釋放該對象鎖,要等到執行notify()方法的線程将程式執行完,也就是退出同步代碼塊之後才會釋放對象鎖。
注意:
喚醒線程不能過早,如果在還沒有線程在等待中時,過早的喚醒線程,這個時候就會出現先喚醒,在等待的效果了。
public class ThreadTest3 {
public static void main(String[] args) {
Object locker = new Object();
Thread t = new Thread() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (locker) {
System.out.println(" t 開始運作");
try {
locker.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" t 運作結束");
}
}
};
t.start();
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (locker) {
System.out.println(" t1 開始運作");
locker.notify();
System.out.println(" t1 運作結束");
}
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
synchronized (locker) {
System.out.println(" t2 開始運作");
System.out.println(" t2 運作結束");
}
}
};
t2.start();
}
}
競态條件問題
當一個 t 線程執行了wait()方法釋放了對象鎖之後 另外一個 t1 線程擷取了鎖然後立即執行了notify()方法區通知 t 線程 但是 t 線程還沒有執行到等待通知的那段代碼 是以這個通知就錯過了 這樣線程 t 就會一直等待下去 這就是一個競态條件問題
競态條件問題
- 為了解決和
避免這個問題 事實上當我們在執行wait() 操作的時候 釋放鎖和等待接收通知是處于一個原子性質上執行的 最後才執行嘗試重新擷取鎖
wait與sleep的對比
- wait用于線程之間的通信 sleep用于讓線程阻塞一段時間
- wait之前必須要有鎖 wait之後會釋放鎖 等被喚醒之後再重嘗試請求鎖
- sleep是無視鎖的存在的 即不會要求有鎖 也不會去釋放鎖
- wait是object的方法
- sleep是thread的靜态方法
- 唯一相同點就是都可以讓線程放棄執行一段時間