bilibili-Java併發學習筆記2 wait 和 notify
基於 java 1.8.0
P6_wait與sleep方法字節碼分析
- wait
- wait()
- 在其他線程調用此對象的 notify() 方法或 notifyAll() 方法前,導致
當前線程等待
。換句話說,此方法的行爲就好像它僅執行 wait(0) 調用一樣。 - 當前線程必須擁有此
monitor
。該線程釋放
對此 monitor 的所有權並等待,直到其他線程通過調用 notify 方法,或 notifyAll 方法通知在此對象的 monitor 上等待的線程醒來。然後該線程將等到重新獲得對監視器的所有權後才能繼續執行。 - 此方法只應由作爲
此對象監視器的所有者
的線程來調用。
- 在其他線程調用此對象的 notify() 方法或 notifyAll() 方法前,導致
- wait(long timeout)
- 此方法導致當前線程(稱之爲 T)將其自身放置在對象的
等待集
中,然後放棄此對象上的所有同步要求。出於線程調度目的,在發生以下四種情況之一前,線程 T 被禁用,且處於休眠狀態:- 其他某個線程調用此對象的 notify 方法,並且線程 T 碰巧被任選爲被喚醒的線程。
- 其他某個線程調用此對象的 notifyAll 方法。
- 其他某個線程中斷線程 T。
- 大約已經到達指定的實際時間。但是,如果 timeout 爲零,則不考慮實際時間,在獲得通知前該線程將一直等待。
- 然後,從對象的
等待集
中刪除線程 T,並重新進行線程調度。然後,該線程以常規方式與其他線程競爭,以獲得在該對象上同步的權利;一旦獲得對該對象的控制權,該對象上的所有其同步聲明都將被恢復到以前的狀態,這就是調用 wait 方法時的情況。然後,線程 T 從 wait 方法的調用中返回。所以,從 wait 方法返回時,該對象和線程 T 的同步狀態與調用 wait 方法時的情況完全相同。 - 在沒有被通知、中斷或超時的情況下,線程還可以喚醒一個所謂的
虛假喚醒
(spurious wakeup)。雖然這種情況在實踐中很少發生,但是應用程序必須通過以下方式防止其發生,即對應該導致該線程被提醒的條件進行測試,如果不滿足該條件,則繼續等待。換句話說,等待應總是發生在循環中
- (有關這一主題的更多信息,請參閱 Doug Lea 撰寫的 Concurrent Programming in Java (Second Edition) (Addison-Wesley, 2000) 中的第 3.2.3 節或 Joshua Bloch 撰寫的 Effective Java Programming Language Guide (Addison-Wesley, 2001) 中的第 50 項。
- http://wavelino.coffeecup.com/pdf/EffectiveJava.pdf
- http://195.122.253.112/public/texts/%D0%BA%D0%BD%D0%B8%D0%B3%D0%B8%20%D0%BF%D0%BE%20%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B8%D1%80%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8E/Effective_Java.pdf
- 如果當前線程在等待之前或在等待時被任何線程中斷,則會拋出 InterruptedException。在按上述形式恢復此對象的鎖定狀態時纔會拋出此異常。
- 注意,由於 wait 方法將當前線程放入了對象的等待集中,所以
它只能解除此對象的鎖定;可以同步當前線程的任何其他對象在線程等待時仍處於鎖定狀態
。 此方法只應由作爲此對象監視器的所有者的線程來調用
。有關線程能夠成爲監視器所有者的方法的描述,請參閱 notify 方法。
- 此方法導致當前線程(稱之爲 T)將其自身放置在對象的
- wait(long timeout, int nanos)
- public final native void wait(long timeout)
- wait()
- notify
- public final native void notify();
- 喚醒在此對象
monitor
上等待的單個線程
。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程
。選擇是任意性
(隨機)的,並在對實現做出決定時發生。線程通過調用其中一個 wait 方法,在對象的監視器上等待。 - 直到當前線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作爲鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。
- 此方法只應由作爲
此對象監視器的所有者
的線程來調用。通過以下三種方法之一,線程可以成爲此對象監視器的所有者
:- 通過執行此對象的 synchronized 實例方法。(對象鎖)
- 通過執行在此對象上進行同步的 synchronized 語句塊。(對象鎖)
- 對於 Class 類型的對象,可以通過執行該類的靜態 synchronized 方法。(類鎖)
- 一次只能有一個線程擁有對象的監視器。
- notifyAll
- public final native void notifyAll();
- 喚醒在此對象
monitor
上等待的所有線程
。線程通過調用其中一個 wait 方法,在對象的監視器上等待。 - 直到當前線程放棄此對象上的鎖定,才能繼續執行被喚醒的線程。被喚醒的線程將以常規方式與在該對象上主動同步的其他所有線程進行競爭;例如,喚醒的線程在作爲鎖定此對象的下一個線程方面沒有可靠的特權或劣勢。
- 此方法只應由作爲此對象監視器的所有者的線程來調用。有關線程能夠成爲監視器所有者的方法的描述,請參閱 notify 方法。
- sleep
- 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。該線程
不丟失任何監視器的所有權
。
- 在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。該線程
package new_package.thread.p5;
public class WaitTest {
public static void main(String[] args) throws InterruptedException {
Object obj = new Object();
synchronized (obj) {
// 換句話說,等待應總是發生在循環中
while (true) {
obj.wait();
//System.out.println("wait after");
}
}
}
}
// javap -v new_package.thread.p5.WaitTest
Code:
stack=2, locals=4, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: aload_1
9: dup
10: astore_2
11: monitorenter
12: aload_1
13: invokevirtual #3 // Method java/lang/Object.wait:()V
16: goto 12
19: astore_3
20: aload_2
21: monitorexit
22: aload_3
23: athrow
P7_notify方法詳解及線程獲取鎖的方式分析
wait 方法應該與 notify 或 notifyAll 結合使用;
P8_wait與notify及線程同步系統總結
- 當調用某對象的 wait 方法時,需要確保調用 wait 方法的線程已經持有了對象(wait方法所屬對象)的鎖;
- 當調用 wait 方法後,該線程會釋放掉這個對象的鎖,然後線程進入等待狀態(進入該對象的等待集合);
- 線程在等待狀態時,需要等待其他線程調用該對象的 notify 或 notifyAll 方法來喚醒自己;
- 一旦這個線程被喚醒後,該線程會與其他線程一同開始競爭這個對象的鎖(公平競爭);只有當該線程獲取到這個對象的鎖後,該線程纔會繼續往下執行;
- 調用 wait 方法的代碼片段需要放在一個 synchronized 語句塊或 synchronized 方法中,這樣才能確保調用 wait 方法之前已經獲取到對象的鎖;
- 當調用 notify 方法時,它會隨機喚醒該對象等待集合中的任意一個線程,當某個線程被喚醒後,它會與其他線程一起重新競爭對象的鎖;
- 當調用對象的 notifyAll 方法時,它會喚醒該對象的等待集合中的所有線程,這些線程會重新開始競爭對象的鎖;
- 在某一時刻,只有唯一一個線程可以擁有對象的鎖;
P9_wait與notify方法案例剖析與詳解
package new_package.thread.p5;
import java.io.IOException;
public class CounterTest {
private volatile int value = 0;
public static void main(String[] args) throws InterruptedException, IOException {
CounterTest counter = new CounterTest();
Thread thread1 = new Thread(() -> {
// +1
while (true) {
synchronized (counter) {
counter.value++;
System.out.print(counter.value);
while (counter.value == 1) {
try {
counter.notifyAll();
counter.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread thread2 = new Thread(() -> {
// -1
while (true) {
synchronized (counter) {
counter.value--;
System.out.print(counter.value);
while (counter.value == 0) {
try {
counter.notifyAll();
counter.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
thread1.start();
thread2.start();
System.in.read();
}
}
package new_package.thread.p5;
import java.util.concurrent.CountDownLatch;
public class CounterTest2 {
private /*volatile*/ int value = 0;
public synchronized void incr() {
while (value != 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
value++;
System.out.print(value);
notifyAll();
}
public synchronized void decr() {
while (value == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
value--;
System.out.print(value);
notifyAll();
}
public static void main(String[] args) throws InterruptedException {
CounterTest2 counter = new CounterTest2();
CountDownLatch countDownLatch = new CountDownLatch(2);
int count = 30;
Thread thread1 = new Thread(() -> {
// +1
// while (true)
for (int i = 0; i < count; i++) {
counter.incr();
}
});
Thread thread2 = new Thread(() -> {
// -1
// while (true)
for (int i = 0; i < count; i++) {
counter.decr();
}
});
thread1.start();
thread2.start();
countDownLatch.await();
}
}
P10_多線程同步關係實例剖析與講解
package new_package.thread.p5;
// 多線程同步
// while
// notifyAll
public class CounterTest2 {
private int value = 0;
public synchronized void incr() {
while (value !=0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
value++;
System.out.print(value);
notifyAll();
}
public synchronized void decr() {
while (value == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
value--;
System.out.print(value);
notifyAll();
}
public static void main(String[] args) {
CounterTest2 counter = new CounterTest2();
int count = 10030;
Thread thread1 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.incr();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.decr();
}
});
Thread thread3 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.incr();
}
});
Thread thread4 = new Thread(() -> {
for (int i = 0; i < count; i++) {
counter.decr();
}
});
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}