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/