Java多線程程式設計-Object wait和notify
- Object.wait()方法
-
- 調用wait方法的時候,目前線程必須擁有這個對象monitor
- Object.notify()方法
- Object.notifyAll()方法
Java多線程環境下,線程與線程之間是可以進行通信的,是可以進行互相互動的。現在我們先從Object對象中的wait方法和notify方法進行說起吧。
Object.wait()方法
導緻目前線程等待,直到另一個線程調用該對象的notify()方法或notifyAll()方法,或者指定的時間已經過去。
目前線程必須擁有這個對象的monitor。
此方法使目前線程将自己置于該對象的等待集中,然後放棄對該對象的任何和所有同步聲明。 出于線程排程的目的,目前線程被禁用,并且處于休眠狀态,直到發生以下四種情況之一:
1.其他一些線程為此對象調用notify方法,并且線程T恰好被任意選擇為要喚醒的線程。
2.其他一些線程為此對象調用notifyAll方法。
3.其他一些線程中斷目前線程。
4.指定的實時量或多或少已經過去。 但是,如果逾時為零,則不考慮實時,線程隻是等待直到通知。
我們等會一個一個的驗證。
然後将目前線程從該對象的等待集中删除,并重新啟用線程排程。 然後,它以通常的方式與其他線程競争在對象上進行同步的權利。 一旦它獲得了對象的控制權,它對對象的所有同步聲明都将恢複到原樣。即,恢複到調用wait方法時的情況。 然後,目前線程從調用wait方法傳回。 是以,從等待方法傳回時,對象和目前線程的同步狀态與調用等待方法時的狀态完全相同。
線程也可以喚醒,而不會被通知,中斷或逾時,即所謂的虛假喚醒。 盡管在實踐中這種情況很少發生,但是應用程式必須通過測試應該導緻線程喚醒的條件來防範它,并在條件不滿足時繼續等待。
如果目前線程在等待之前或等待期間被任何線程中斷,則抛出InterruptedException。 如上所述,直到該對象的鎖定狀态恢複之前,不會引發此異常。
注意,wait方法将目前線程放入此對象的等待集中,是以隻會解鎖該對象; 當線程等待時,目前線程可以在其上同步的所有其他對象保持鎖定。
此方法隻能由作為該對象的螢幕的所有者的線程調用。
wait源碼:
public final void wait() throws InterruptedException {
wait(0);
}
調用wait方法的時候,目前線程必須擁有這個對象monitor
我們使用簡單的一個方法驗證一下,使用main線程進行驗證
Main方法:
public class ThreadWaitMain {
public static void main(String[] args) {
try {
System.out.println("current thread name:" + Thread.currentThread().getName());
Object obj = new Object();
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運作結果:
這裡會抛出IllegalMonitorStateException異常,出現這個異常的原因是Main線程試圖在obj對象的螢幕上等待,或者通知其他線程在obj對象monitor上等待而沒有擁有指定的monitor。
我們怎麼解決這個問題呢?将目前Main線程擷取目前對象的鎖就可以解決。
public class ThreadWaitMainV2 {
public static void main(String[] args) {
try {
Object obj = new Object();
System.out.println("current thread name:" + Thread.currentThread().getName());
synchronized (obj) {
System.out.println("go in synchronized code block,get obj monitor");
obj.wait();
System.out.println("this code is wait back");
}
System.out.println("end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
運作結果:
有運作可以看出,沒有抛出上面的異常并且wait方法後面的代碼都沒有執行,沒有列印出this code is wait back和end,這裡程式将一直處于等待,直到有人調用了notify方法或者notifyAll方法或者到達指定的時間,後面我們會将notify方法和notifyAll方法。
我們将上面的wait方法改為wait(2000)試試:
運作結果:
列印了相關的語句,說明我們在wait的時候可以指定等待的時間,到達時間之後将繼續執行。
Object.notify()方法
喚醒正在此對象的螢幕上等待的單個線程。 如果有任何線程在此對象上等待,則選擇其中一個喚醒。 該選擇是任意的,并且可以根據實作情況進行選擇。 線程通過調用其中一個wait方法在對象的螢幕上等待。
在目前線程放棄對該對象的鎖定之前,喚醒的線程将無法繼續。 喚醒的線程将以通常的方式與可能正在主動競争以在此對象上進行同步的任何其他線程競争。
此方法隻能由作為該對象的螢幕的所有者的線程調用。 線程通過以下三種方式之一成為對象螢幕的所有者:
1.通過執行該對象的同步執行個體方法。
2.通過執行在對象上同步的同步語句的主體。
3.對于類類型的對象,通過執行該類的同步靜态方法。
一次隻能有一個線程擁有對象的monitor。
notify源碼:
上面談到wait的時候,喚醒需要使用notify進行喚醒,我們驗證一下:
線程1:
public class ThreadWaitT1 extends Thread {
private Object obj;
public ThreadWaitT1(Object obj, String threadName) {
this.obj = obj;
this.setName(threadName);
}
@Override
public void run() {
try {
synchronized (obj) {
System.out.println("start thread,current thread name:" + Thread.currentThread().getName());
obj.wait();
System.out.println("end thread,current thread name:" + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
線程2:
public class ThreadWaitT2 extends Thread {
private Object obj;
public ThreadWaitT2(Object obj, String threadName) {
this.obj = obj;
this.setName(threadName);
}
@Override
public void run() {
synchronized (obj) {
System.out.println("start thread,current thread name:" + Thread.currentThread().getName());
obj.notify();
System.out.println("end thread,current thread name:" + Thread.currentThread().getName());
}
}
}
運作代碼:
public class ThreadWaitMainV3 {
public static void main(String[] args) {
try {
Object obj = new Object();
ThreadWaitT1 threadWaitT1 = new ThreadWaitT1(obj, "thread01");
ThreadWaitT2 threadWaitT2 = new ThreadWaitT2(obj, "thread02");
threadWaitT1.start();
Thread.sleep(2000);
threadWaitT2.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
運作結果:
在運作之後等2秒之後,才會輸出紅色空裡面的代碼,通過notify喚醒wait的線程。
每一個對象都有兩個隊列,一個是就緒隊列,一個阻塞隊列。就緒隊列存儲了将要獲得鎖的線程,阻塞隊列存儲了被阻塞的線程。
我們進行一下驗證notify()方法不會立即釋放鎖:
線程對象:
public class ThreadNotifyObj {
public void testFun(Object obj) {
try {
synchronized (obj) {
System.out.println("current thread name:" + Thread.currentThread().getName() + " start wait()");
obj.wait();
System.out.println("current thread name:" + Thread.currentThread().getName() + " end wait()");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void testNotifyFun(Object obj) {
try {
synchronized (obj) {
System.out.println("current thread name:" + Thread.currentThread().getName() + " start notify()");
System.out.println("time:" + System.currentTimeMillis());
obj.notify();
Thread.sleep(2000);
System.out.println("current thread name:" + Thread.currentThread().getName() + " end notify()");
System.out.println("time:" + System.currentTimeMillis());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
線程1:
public class ThreadNotifyT1 extends Thread {
private Object object;
public ThreadNotifyT1(Object obj, String threadName) {
this.object = obj;
this.setName(threadName);
}
@Override
public void run() {
ThreadNotifyObj threadNotifyObj = new ThreadNotifyObj();
threadNotifyObj.testFun(object);
}
}
線程2:
public class ThreadNotifyT2 extends Thread {
private Object object;
public ThreadNotifyT2(Object obj, String threadName) {
this.object = obj;
this.setName(threadName);
}
@Override
public void run() {
ThreadNotifyObj threadNotifyObj = new ThreadNotifyObj();
threadNotifyObj.testNotifyFun(object);
}
}
線程3:
public class ThreadNotifyT3 extends Thread {
private Object obj;
public ThreadNotifyT3(Object obj, String threadName) {
this.obj = obj;
this.setName(threadName);
}
@Override
public void run() {
ThreadNotifyObj threadNotifyObj = new ThreadNotifyObj();
threadNotifyObj.testNotifyFun(obj);
}
}
運作結果:
由上面的運作可以看出,notify釋放鎖必須等執行完notify方法所在的同步代碼塊後才釋放鎖。
我們在使用wait的時候能調用中斷的方法嗎?答案是肯定的,是不行的。
public class ThreadWaitInterruptedMain {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Thread thread = new Thread(() -> {
try {
synchronized (obj) {
obj.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("發生了異常");
}
});
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
運作結果:
說明在wait中的線程中斷的時候會抛出異常導緻線程終止并釋放鎖。
Object.notifyAll()方法
喚醒正在此對象的螢幕上等待的所有線程。 線程通過調用其中一個wait方法在對象的螢幕上等待。
在目前線程放棄對該對象的鎖定之前,喚醒的線程将無法繼續。 喚醒的線程将以通常的方式與可能正在主動競争以在此對象上進行同步的任何其他線程競争。 例如,被喚醒的線程在成為鎖定該對象的下一個線程時,不會享有任何可靠的特權或劣勢。
notifyAll()隻能由作為該對象的螢幕的所有者的線程調用
notifyAll方法源碼:
我們這是簡單驗證一下:
public class ThreadNotifyAllMain {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
Stream.of("thread01", "thread02", "thread03").forEach(threadName -> {
new Thread(() -> {
try {
synchronized (obj) {
System.out.println("start wait by thread name:" + Thread.currentThread().getName());
obj.wait();
System.out.println("end wait by thread name:" + Thread.currentThread().getName());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, threadName).start();
});
Thread.sleep(2000);
synchronized (obj) {
obj.notifyAll();
}
}
}
運作結果:
先回輸出上面三行,等2秒之後會有main全部喚醒正在等待的線程。