wait 和 notify 有坑。。
AI全套:Python3+TensorFlow打造人臉識別智能小程序
最新人工智能資料-Google工程師親授 Tensorflow-入門到進階
黑馬頭條項目 - Java Springboot2.0(視頻、資料、代碼和講義)14天完整版
作者:忘凈空
鏈接:https://www.jianshu.com/p/91d95bb5a4bd
也許我們只知道wait和notify是實現(xiàn)線程通信的,同時要使用synchronized包住,其實在開發(fā)中知道這個是遠遠不夠的。
問題一:通知丟失
創(chuàng)建2個線程,一個線程負責計算,一個線程負責獲取計算結(jié)果。
public class Calculator extends Thread {
int total;
@Override
public void run() {
synchronized (this){
for(int i = 0; i < 101; i++){
total += i;
}
this.notify();
}
}
}
public class ReaderResult extends Thread {
Calculator c;
public ReaderResult(Calculator c) {
this.c = c;
}
@Override
public void run() {
synchronized (c) {
try {
System.out.println(Thread.currentThread() + "等待計算結(jié)...");
c.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "計算結(jié)果為:" + c.total);
}
}
public static void main(String[] args) {
Calculator calculator = new Calculator();
//先啟動獲取計算結(jié)果線程
new ReaderResult(calculator).start();
calculator.start();
}
}我們會獲得預(yù)期的結(jié)果:
Thread[Thread-1,5,main]等待計算結(jié)...
Thread[Thread-1,5,main]計算結(jié)果為:5050但是我們修改為先啟動計算線程呢?
calculator.start();
new ReaderResult(calculator).start();這是獲取結(jié)算結(jié)果線程一直等待:
Thread[Thread-1,5,main]等待計算結(jié)...問題分析
打印出線程堆棧:
"Thread-1" prio=5 tid=0x00007f983b87e000 nid=0x4d03 in Object.wait() [0x0000000118988000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x00000007d56fb4d0> (a com.concurrent.waitnotify.Calculator)
at java.lang.Object.wait(Object.java:503)
at com.concurrent.waitnotify.ReaderResult.run(ReaderResult.java:18)
- locked <0x00000007d56fb4d0> (a com.concurrent.waitnotify.Calculator)可以看出ReaderResult在Calculator上等待。
發(fā)生這個現(xiàn)象就是常說的通知丟失,在獲取通知前,通知提前到達,我們先計算結(jié)果,計算完后再通知。
public class Calculator extends Thread {
int total;
boolean isSignalled = false;
@Override
public void run() {
synchronized (this) {
isSignalled = true;//已經(jīng)通知過
for (int i = 0; i < 101; i++) {
total += i;
}
this.notify();
}
}
}
public class ReaderResult extends Thread {
Calculator c;
public ReaderResult(Calculator c) {
this.c = c;
}
@Override
public void run() {
synchronized (c) {
if (!c.isSignalled) {//判斷是否被通知過
try {
System.out.println(Thread.currentThread() + "等待計算結(jié)...");
c.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "計算結(jié)果為:" + c.total);
}
}
}
public static void main(String[] args) {
Calculator calculator = new Calculator();
new ReaderResult(calculator).start();
calculator.start();
}
}問題二:假喚醒
兩個線程去刪除數(shù)組的元素,當沒有元素的時候等待,另一個線程添加一個元素,添加完后通知刪除數(shù)據(jù)的線程。
public class EarlyNotify{
private List list;
public EarlyNotify() {
list = Collections.synchronizedList(new LinkedList());
}
public String removeItem() throws InterruptedException {
synchronized ( list ) {
if ( list.isEmpty() ) { //問題在這
list.wait();
}
//刪除元素
String item = (String) list.remove(0);
return item;
}
}
public void addItem(String item) {
synchronized ( list ) {
//添加元素
list.add(item);
//添加后,通知所有線程
list.notifyAll();
}
}
private static void print(String msg) {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + msg);
}
public static void main(String[] args) {
final EarlyNotify en = new EarlyNotify();
Runnable runA = new Runnable() {
public void run() {
try {
String item = en.removeItem();
} catch ( InterruptedException ix ) {
print("interrupted!");
} catch ( Exception x ) {
print("threw an Exception!!!\n" + x);
}
}
};
Runnable runB = new Runnable() {
public void run() {
en.addItem("Hello!");
}
};
try {
//啟動第一個刪除元素的線程
Thread threadA1 = new Thread(runA, "threadA1");
threadA1.start();
Thread.sleep(500);
//啟動第二個刪除元素的線程
Thread threadA2 = new Thread(runA, "threadA2");
threadA2.start();
Thread.sleep(500);
//啟動增加元素的線程
Thread threadB = new Thread(runB, "threadB");
threadB.start();
Thread.sleep(1000); // wait 10 seconds
threadA1.interrupt();
threadA2.interrupt();
} catch ( InterruptedException x ) {}
}
}threadA1: threw an Exception!!!
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0這里發(fā)生了假喚醒,當添加完一個元素然后喚醒兩個線程去刪除,這個只有一個元素,所以會拋出數(shù)組越界,這時我們需要喚醒的時候在判斷一次是否還有元素。Java 最新技術(shù)教程和示例源碼參考:https://github.com/javastacks/javastack
修改代碼:
public String removeItem() throws InterruptedException {
synchronized ( list ) {
while ( list.isEmpty() ) { //問題在這
list.wait();
}
//刪除元素
String item = (String) list.remove(0);
return item;
}
}等待/通知的典型范式
從上面的問題我們可歸納出等待/通知的典型范式。
該范式分為兩部分,分別針對等待方(消費者)和通知方(生產(chǎn)者)。
等待方遵循原則如下:
獲取對象的鎖 如果條件不滿足,那么調(diào)用對象的wait()方法,被通知后仍要檢查條件 條件滿足則執(zhí)行對應(yīng)的邏輯 對應(yīng)偽代碼如下:
synchronized(對象){
while(條件不滿足){
對象.wait();
}
對應(yīng)的處理邏輯
}通知方遵循原則如下:
獲得對象的鎖 改變條件 通知所以等待在對象上的線程 對應(yīng)偽代碼如下:
synchronized(對象){
改變條件
對象.notifyAll();
}
看完本文有收獲?請轉(zhuǎn)發(fā)分享給更多人
往期資源:
2019最新Python視頻:從入門到Swiper項目實戰(zhàn)
2019重磅高級資源:Java并發(fā)編程原理和實戰(zhàn)
最新黑馬大數(shù)據(jù)資源:深入解析docker容器化技術(shù)
最新Java后端實戰(zhàn)視頻:SSM框架在線商城系統(tǒng)
2019最新黑客技術(shù)之Windows網(wǎng)絡(luò)安全精講
