wait常常被用於生產者和消費者模式,如圖:java
代碼參考:編程
import java.util.Random;
import java.util.Vector;
/** * @ClassName WaitDemo2 * @Description TODO * @Author 鏗然一葉 * @Date 2019/10/3 11:43 * @Version 1.0 * javashizhan.com **/
public class WaitDemo2 {
public static void main(String[] args) {
//初始化任務隊列
TaskQueue taskQueue = new TaskQueue();
//啓動任務consumer
for (int i = 0; i < 4; i++) {
new Thread(new Consumer(taskQueue)).start();
}
//休眠一段時間等到consumer都啓動好
sleep(2000);
//啓動任務生產者Producer
new Thread(new Producer(taskQueue)).start();
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//任務生產者
class Producer implements Runnable {
private TaskQueue taskQueue;
public Producer(TaskQueue taskQueue) {
this.taskQueue = taskQueue;
}
public void run() {
while(true) {
generateTask();
sleep(2000);
}
}
//生成任務
private void generateTask() {
int taskNum = (int)(Math.random()*5+1);
long timestamp = System.currentTimeMillis();
for (int i = 0; i < taskNum; i++) {
String task = "Task_" + timestamp + "_" + i;
taskQueue.addTask(task);
}
}
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//任務消費者
class Consumer implements Runnable {
private TaskQueue taskQueue;
public Consumer(TaskQueue taskQueue) {
this.taskQueue = taskQueue;
}
public void run() {
execTask();
}
private void execTask() {
while (true) {
//獲取任務,若是獲取不到,會進入wait隊列
String task = taskQueue.removeTask();
//任務不爲null則模擬執行
if (null != task) {
System.out.println(task + " be done. Caller is " + Thread.currentThread().getName());
}
}
}
}
//任務隊列
class TaskQueue {
private Vector<String> taskVector = new Vector<String>();
//添加任務
public synchronized void addTask(String task) {
System.out.println(task + " has generated.");
taskVector.add(task);
//喚醒Consumer
this.notify();
}
//移除任務
public synchronized String removeTask() {
if (!taskVector.isEmpty()) {
return taskVector.remove(0);
} else {
try {
System.out.println(Thread.currentThread().getName() + " waiting...");
//沒有任務則進入等待隊列
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
}
複製代碼
運行日誌:緩存
Thread-0 waiting...
Thread-3 waiting...
Thread-2 waiting...
Thread-1 waiting...
Task_1570104227120_0 has generated.
Task_1570104227120_1 has generated.
Task_1570104227120_0 be done. Caller is Thread-0
Task_1570104227120_2 has generated.
Task_1570104227120_3 has generated.
Task_1570104227120_1 be done. Caller is Thread-3
Task_1570104227120_3 be done. Caller is Thread-1
Thread-2 waiting...
Task_1570104227120_2 be done. Caller is Thread-0
Thread-1 waiting...
Thread-3 waiting...
Thread-0 waiting...
Task_1570104229120_0 has generated.
Task_1570104229120_1 has generated.
Task_1570104229120_2 has generated.
Task_1570104229120_3 has generated.
Task_1570104229120_4 has generated.
Task_1570104229120_0 be done. Caller is Thread-2
Task_1570104229120_2 be done. Caller is Thread-2
Task_1570104229120_4 be done. Caller is Thread-2
Thread-2 waiting...
Task_1570104229120_1 be done. Caller is Thread-0
Thread-1 waiting...
Thread-0 waiting...
Task_1570104229120_3 be done. Caller is Thread-3
Thread-3 waiting...
Task_1570104231121_0 has generated.
Task_1570104231121_1 has generated.
Task_1570104231121_2 has generated.
Task_1570104231121_0 be done. Caller is Thread-2
Thread-2 waiting...
Task_1570104231121_1 be done. Caller is Thread-0
Thread-0 waiting...
Task_1570104231121_2 be done. Caller is Thread-1
Thread-1 waiting...
複製代碼
從日誌能夠看出,生成的任務數和線程被調用次數是相等的。安全
咱們經過一副圖來理解this.wait:bash
注:不少人容易犯的錯誤是誰調用了wait,那麼誰就進入wait隊列,而實際上進入wait隊列的應該是調用線程,而不是以下的obj。併發
經過jstack命令查看堆棧信息能夠驗證這一點:app
2019-10-03 13:04:52
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.40-b25 mixed mode):
"DestroyJavaVM" #16 prio=5 os_prio=0 tid=0x00000000035b2800 nid=0x8fe0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-3" #15 prio=5 os_prio=0 tid=0x000000001f1cd800 nid=0x324c in Object.wait() [0x00000000200ae000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076bedff18> (a com.javashizhan.concurrent.demo.base.TaskQueue)
at java.lang.Object.wait(Object.java:502)
at com.javashizhan.concurrent.demo.base.TaskQueue.removeTask(WaitDemo2.java:108)
- locked <0x000000076bedff18> (a com.javashizhan.concurrent.demo.base.TaskQueue)
at com.javashizhan.concurrent.demo.base.Consumer.execTask(WaitDemo2.java:84)
at com.javashizhan.concurrent.demo.base.Consumer.run(WaitDemo2.java:79)
at java.lang.Thread.run(Thread.java:745)
"Thread-2" #14 prio=5 os_prio=0 tid=0x000000001f1cd000 nid=0x84d0 in Object.wait() [0x000000001ffaf000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076bedff18> (a com.javashizhan.concurrent.demo.base.TaskQueue)
at java.lang.Object.wait(Object.java:502)
at com.javashizhan.concurrent.demo.base.TaskQueue.removeTask(WaitDemo2.java:108)
- locked <0x000000076bedff18> (a com.javashizhan.concurrent.demo.base.TaskQueue)
at com.javashizhan.concurrent.demo.base.Consumer.execTask(WaitDemo2.java:84)
at com.javashizhan.concurrent.demo.base.Consumer.run(WaitDemo2.java:79)
at java.lang.Thread.run(Thread.java:745)
"Thread-1" #13 prio=5 os_prio=0 tid=0x000000001f1cb000 nid=0x4404 in Object.wait() [0x000000001feae000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076bedff18> (a com.javashizhan.concurrent.demo.base.TaskQueue)
at java.lang.Object.wait(Object.java:502)
at com.javashizhan.concurrent.demo.base.TaskQueue.removeTask(WaitDemo2.java:108)
- locked <0x000000076bedff18> (a com.javashizhan.concurrent.demo.base.TaskQueue)
at com.javashizhan.concurrent.demo.base.Consumer.execTask(WaitDemo2.java:84)
at com.javashizhan.concurrent.demo.base.Consumer.run(WaitDemo2.java:79)
at java.lang.Thread.run(Thread.java:745)
"Thread-0" #12 prio=5 os_prio=0 tid=0x000000001f0d8800 nid=0x6750 in Object.wait() [0x000000001fdae000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076bedff18> (a com.javashizhan.concurrent.demo.base.TaskQueue)
at java.lang.Object.wait(Object.java:502)
at com.javashizhan.concurrent.demo.base.TaskQueue.removeTask(WaitDemo2.java:108)
- locked <0x000000076bedff18> (a com.javashizhan.concurrent.demo.base.TaskQueue)
at com.javashizhan.concurrent.demo.base.Consumer.execTask(WaitDemo2.java:84)
at com.javashizhan.concurrent.demo.base.Consumer.run(WaitDemo2.java:79)
at java.lang.Thread.run(Thread.java:745)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000001f123800 nid=0x60ac runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000000001f099000 nid=0x8094 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001f084800 nid=0x91d0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001f07f000 nid=0x8f44 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001f07b000 nid=0x9034 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001f056800 nid=0x47dc runnable [0x000000001f6ae000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:170)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076c010b70> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x000000076c010b70> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001efe9000 nid=0x8514 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001f038000 nid=0x861c runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x00000000036aa000 nid=0x6f48 in Object.wait() [0x000000001efaf000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076bc06f58> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
- locked <0x000000076bc06f58> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x00000000036a3000 nid=0x6e7c in Object.wait() [0x000000001eeaf000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076bc06998> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:157)
- locked <0x000000076bc06998> (a java.lang.ref.Reference$Lock)
"VM Thread" os_prio=2 tid=0x000000001cfea000 nid=0x1780 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000035c8000 nid=0x8a28 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000035c9800 nid=0x8e94 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000035cb000 nid=0x9128 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000035cd800 nid=0x8f60 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00000000035d0000 nid=0xec0 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00000000035d1000 nid=0x9100 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00000000035d4000 nid=0x4104 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00000000035d5800 nid=0x6f44 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001f0d7000 nid=0x7978 waiting on condition
JNI global references: 22
複製代碼
以下代碼說明是線程Consumer進入了wait隊列:dom
"Thread-3" #15 prio=5 os_prio=0 tid=0x000000001f1cd800 nid=0x324c in Object.wait() [0x00000000200ae000]
java.lang.Thread.State: WAITING (on object monitor)
複製代碼
1.notify喚醒隊列中的一個等待對象
2.notifyAll喚醒隊列中的全部等待對象socket
在上述例子中任務是一個個添加的,所以調用notify沒有問題;若是批量添加任務,只調用一次notify,那麼就可能出現只有一個consumer被喚醒處理任務,其餘consumer被餓死;而若是添加一個任務就調用notifyAll,那麼會無謂的喚醒多餘的Consumer,沒有任務可執行的Consumer被喚醒後,又當即進入wait隊列。post
不少時候了避免consumer被意外餓死,保險起見都統一調用notifyAll而不是notify,實際也不至於都如此,只要理解了原理,合理分析就能夠知道應該調用哪一個。
判斷使用notify的依據有:
1.全部等待線程擁有相同的等待條件;
2.全部等待線程被喚醒後,執行相同的操做;
3.只須要喚醒一個線程。
1.wait會釋放鎖而sleep不會
2.wait只能在synchronized代碼塊中執行,而sleep沒有限制
3.wait的使用更像事件監聽機制,工做線程監聽某個事件(如任務隊列),事件到達後通知工做線程,而sleep的使用更像輪詢機制,不斷的輪詢任務隊列中是否又任務。在處理任務隊列這個場景上使用wait更優一些。
1.wait和notify,notifyAll只能出如今synchronized代碼塊中
2.obj.wait()方法基於obj對象生成了一個wait隊列
3.調用obj.wait的同步代碼塊的線程進入了等待隊列,而不是obj進入等待隊列
4.使用notify和notifyAll要根據實際場景具體分析
5.任務隊列場景wait優於sleep,可避免沒必要要的輪詢。
end.
相關閱讀:
Java併發編程(一)知識地圖
Java併發編程(二)原子性
Java併發編程(三)可見性
Java併發編程(四)有序性
Java併發編程(五)建立線程方式概覽
Java併發編程入門(六)synchronized用法
Java併發編程入門(八)線程生命週期
Java併發編程入門(九)死鎖和死鎖定位
Java併發編程入門(十)鎖優化
Java併發編程入門(十一)限流場景和Spring限流器實現
Java併發編程入門(十二)生產者和消費者模式-代碼模板
Java併發編程入門(十三)讀寫鎖和緩存模板
Java併發編程入門(十四)CountDownLatch應用場景
Java併發編程入門(十五)CyclicBarrier應用場景
Java併發編程入門(十六)秒懂線程池差異
Java併發編程入門(十七)一圖掌握線程經常使用類和接口
Java併發編程入門(十八)再論線程安全
Java極客站點: javageektour.com/