共计 13739 个字符,预计需要花费 35 分钟才能阅读完成。
一、前言
说起 java 的线程之间的通信,难免会想起它,他就是 wait、notify、notifyAll
- 他们三个都是 Object 类的办法,受到 final 和 native 加持,也就造就了他们是不能被重写的
- wait() 期待,象征让出以后线程的锁,进入期待状态,让其余线程先用会儿锁,这里留神了,什么叫让出以后线程的锁?也就是你以后线程必须要先取得锁,所以它个别会与 synchronized(我的上一篇文章有写)配合应用
官网正文:The current thread must own this object’s monitor.
wait 要抛出 InterruptedException 异样 须要 try catch 因为线程 wait 期间可能会被打断。 - notify() 唤醒一个 wait()的线程,当 notify 所在的代码块的锁开释之后,wait 的线程开始抢锁,嗯 ……. ,Object 类里正文写的是唤醒 wait 线程是任意 (arbitrary) 的 , 然而能够由具体实现自行裁决,我看 hotspot 实现如同是用的双向链表,notify 的时候是从 head 拿出一个唤醒,所以我称之为有序, 如果有问题请读者指出。
- notifyAll () 唤醒所有 wait 线程,notify 的高级版本
- 注意事项:并不是说 notify 之后 wait 的线程就能马上执行,因为 wait 是放弃了以后线程的锁,被 notify 之后还须要本人去抢锁,如果 notify 所在的代码块还没有抢到锁,或者被其余线程把锁抢到了,那 wait 所在线程还须要接着致力抢锁。
二、DEMO
1.wait notify 简略应用
是这样的, 小明做了饭,给二月鸟吃(备注:二月鸟 是一个人名),只有一双筷子,小明须要先尝一口看能不能吃,而后再告诉二月鸟吃饭,二月鸟要等小明放下筷子能力拿起筷子吃饭。用程序怎么实现,实现形式很多 咱明天只论 wait notify 其余形式靠边儿站
public class WaitNotifyTest {public static void main(String[] args) { | |
// 这是一把锁 筷子 | |
Object obj = new Object(); | |
new Thread(()->{synchronized (obj){ | |
try {System.out.println("二月鸟来了 等着吃饭..."); | |
obj.wait(); | |
System.out.println("二月鸟拿到筷子吃饭喽..."); | |
} catch (InterruptedException e) {e.printStackTrace(); | |
} | |
} | |
}).start(); | |
new Thread(()->{synchronized (obj){ | |
try{System.out.println("小明尝一下能够吃,告诉大家吃饭..."); | |
// 告诉二月鸟 能够吃饭了 | |
obj.notify(); | |
System.out.println("这个时候小明还没有放下筷子..."); | |
TimeUnit.SECONDS.sleep(5); | |
System.out.println("小明放筷子了..."); | |
} catch (Exception e) {System.out.println("中毒了.."); | |
} | |
} | |
}).start();} | |
} | |
执行后果:二月鸟来了 等着吃饭... | |
小明尝一下能够吃,告诉大家吃饭... | |
这个时候小明还没有放下筷子... | |
小明放筷子了... | |
二月鸟拿到筷子吃饭喽... |
这里须要留神几个点:
- wait 须要在 synchronized 中包裹着
- notify 须要 synchronized 中包裹着
- notify 之后 二月鸟没有马上拿起筷子吃饭,因为小明还没有放下筷子(锁还没开释)
- 这个故事里,小明有点儿不纯粹了,他还没筹备放筷子就告诉二月鸟能够吃饭了,害的二月鸟等了半天,咱们不能学小明,咱们平时写代码,个别业务执行完了,代码块最初执行 notify,执行完 notify 之后线程马上就会开释锁。
2. wait notifyAll 简略应用
还是 1 中的例子,小明做完饭后,二月鸟和小月月都来吃饭了, 还是只有一双筷子(真穷),这时候咱们用 wait notify 试一下 大家看看
public class WaitNotifyTest02 {public static void main(String[] args) { | |
// 这是一把锁 筷子 | |
Object obj = new Object(); | |
new Thread(()->{synchronized (obj){ | |
try {System.out.println("二月鸟来了 等着吃饭..."); | |
obj.wait(); | |
System.out.println("二月鸟拿到筷子吃饭喽..."); | |
} catch (InterruptedException e) {e.printStackTrace(); | |
}finally {System.out.println("二月鸟吃完了 放下筷子..."); | |
} | |
} | |
}).start(); | |
new Thread(()->{synchronized (obj){ | |
try {System.out.println("小月月来了 等着吃饭..."); | |
obj.wait(); | |
System.out.println("小月月拿到筷子吃饭喽..."); | |
} catch (InterruptedException e) {e.printStackTrace(); | |
}finally {System.out.println("小月月吃完了 放下筷子..."); | |
} | |
} | |
}).start(); | |
new Thread(()->{synchronized (obj){ | |
try{System.out.println("小明尝一下能够吃,告诉大家吃饭..."); | |
// 告诉期待吃饭的一个人(留神是一个人)能够吃饭了 | |
obj.notify(); | |
System.out.println("这个时候小明还没有放下筷子..."); | |
TimeUnit.SECONDS.sleep(5); | |
System.out.println("小明放筷子了..."); | |
} catch (Exception e) {System.out.println("中毒了.."); | |
} | |
} | |
}).start();} | |
} | |
执行后果:二月鸟来了 等着吃饭... | |
小月月来了 等着吃饭... | |
小明尝一下能够吃,告诉大家吃饭... | |
这个时候小明还没有放下筷子... | |
小明放筷子了... | |
二月鸟拿到筷子吃饭喽... | |
二月鸟吃完了 放下筷子... |
咦?小月月怎么不吃饭,二月鸟放下筷子了呀!
- notify 只告诉一个 wait 线程完结 wait 状态
- 这里能够看出 hotspot 实现 是依照 wait 的先后顺序告诉的
- 尽管是依照程序告诉的,然而咱们不能依赖这个法则,因为他仅仅是法则,在别的零碎(可能装置不同的 JVM 实现)上不肯定有这个法则
其余都不变,notify 改为 notifyAll
new Thread(()->{synchronized (obj){ | |
try{System.out.println("小明尝一下能够吃,告诉大家吃饭..."); | |
// 告诉期待吃饭的人,所有人 能够吃饭了 | |
obj.notifyAll(); | |
System.out.println("这个时候小明还没有放下筷子..."); | |
TimeUnit.SECONDS.sleep(5); | |
System.out.println("小明放筷子了..."); | |
} catch (Exception e) {System.out.println("中毒了.."); | |
} | |
} | |
}).start(); | |
执行后果:二月鸟来了 等着吃饭... | |
小月月来了 等着吃饭... | |
小明尝一下能够吃,告诉大家吃饭... | |
这个时候小明还没有放下筷子... | |
小明放筷子了... | |
小月月拿到筷子吃饭喽... | |
小月月吃完了 放下筷子... | |
二月鸟拿到筷子吃饭喽... | |
二月鸟吃完了 放下筷子... |
这里能够看到:小月月竟然比二月鸟先吃到饭,这里是因为 notifyAll 是唤醒了所有人,谁抢到筷子(锁),谁先吃(执行)
通过我的测试,我发现大略法则是依照 wait 的反向程序来的,也就是先 wait 的后吃饭
三、伪装学术讨论
3.1 hotspot 实现,notify 是按 wait 程序的?
<u> 以下内容非虚构,但纯属个人见解,请勿当做实践来用,可作为参考 </u>
3.1.1 hotspot wait 代码(删减版)
// millis:wait 超时工夫 interruptible:是否可中断 TRAPS:调用 wait 的线程 | |
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { | |
Thread * const Self = THREAD; | |
JavaThread * jt = Self->as_Java_thread(); | |
// 这个办法就是判断以后线程是否取得了锁,如果不在 synchronized 代码块就会抛异样 | |
// 看 check_owner 办法 | |
CHECK_OWNER(); | |
// 一堆代码 | |
// 貌似是申请锁 | |
Thread::SpinAcquire(&_WaitSetLock, "WaitSet - add"); | |
// 这个就是把 wait 的线程放到一个“汇合”里 看 AddWaiter 办法 | |
AddWaiter(&node); | |
// 貌似是开释锁 | |
Thread::SpinRelease(&_WaitSetLock); | |
// 一堆代码 | |
} |
// Returns true if the specified thread owns the ObjectMonitor. | |
// 翻译:如果指定的线程领有 ObjectMonitor 也就是取得了锁 就返回 true | |
// Otherwise returns false and throws IllegalMonitorStateException | |
// 翻译:否则返回 false 并且抛出异样 IllegalMonitorStateException | |
// (IMSE). If there is a pending exception and the specified thread | |
// is not the owner, that exception will be replaced by the IMSE. | |
// 这句不会翻译了 | |
bool ObjectMonitor::check_owner(Thread* THREAD) {void* cur = owner_raw(); | |
if (cur == THREAD) {return true;} | |
if (THREAD->is_lock_owned((address)cur)) {set_owner_from_BasicLock(cur, THREAD); // Convert from BasicLock* to Thread*. | |
_recursions = 0; | |
return true; | |
} | |
// 这里抛出了异样 | |
THROW_MSG_(vmSymbols::java_lang_IllegalMonitorStateException(), | |
"current thread is not owner", false); | |
} |
抛异样示例:
public class WaitNotifyTest04 {public static void main(String[] args) {Object obj = new Object(); | |
new Thread(()->{ | |
try {obj.wait(); | |
} catch (InterruptedException e) {e.printStackTrace(); | |
} | |
}).start();} | |
} | |
异样信息:Exception in thread "Thread-0" | |
java.lang.IllegalMonitorStateException | |
at java.lang.Object.wait(Native Method) | |
at java.lang.Object.wait(Object.java:502) | |
at WaitNotifyTest04.lambda$main$0(WaitNotifyTest04.java:23) | |
at java.lang.Thread.run(Thread.java:748) |
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {// put node at end of queue (circular doubly linked list) | |
// 翻译:把 node 放到队列的最初边(循环双向链表)// 如果你不晓得什么是循环双向链表 我给你画进去 | |
// _WaitSet 是头元素 其实玩儿过链表的人 这里应该都很分明 | |
// 这里所谓的头 不是真正的头 只是一个绝对概念 | |
// 如果是空的 那就一个 waiter ,_next _prev 都指向本人 | |
if (_WaitSet == NULL) { | |
_WaitSet = node; | |
node->_prev = node; | |
node->_next = node; | |
} else { | |
// 如果曾经有了元素 那就把 node 放最初 | |
// 而后 node 的_next 指向_WaitSet 也就是头元素 | |
// node 的_prev 指向增加之前的头元素 | |
ObjectWaiter* head = _WaitSet; | |
ObjectWaiter* tail = head->_prev; | |
assert(tail->_next == head, "invariant check"); | |
tail->_next = node; | |
head->_prev = node; | |
node->_next = head; | |
node->_prev = tail; | |
} | |
} |
循环双向链表:
不晓得大家有没有据说过 Disruptor 这个框架 , 他如同就是相似的构造,这个框架把性能做到了极致,有工夫大家能够理解下
3.1.2 hotspot notify 代码(删减版):
void ObjectMonitor::notify(TRAPS) {CHECK_OWNER(); // 检测是否领有锁 | |
// 如果没有 wait 线程 间接返回 | |
// 这也是为什么 一个线程先 notify 另一个线程再 wait 是不能唤醒的 必须是先 wait 的线程能力被 notify | |
// | |
if (_WaitSet == NULL) {return;} | |
DTRACE_MONITOR_PROBE(notify, this, object(), THREAD); | |
// 次要看这个 | |
INotify(THREAD); | |
OM_PERFDATA_OP(Notifications, inc(1)); | |
} |
// Consider: | |
// If the lock is cool (cxq == null && succ == null) and we're on an MP system | |
// 翻译:如果锁符合条件(cxq == null && succ == null) 并且在一个 MP 零碎 | |
// 说实话 我翻译不上来了,次要看下边这句 | |
// then instead of transferring a thread from the WaitSet to the EntryList | |
// 翻译:咱们将从 WaitSet 中取一个线程到 EntryList | |
// EntryList 里是啥 就是唤醒的线程汇合 | |
// we might just dequeue a thread from the WaitSet and directly unpark() it. | |
void ObjectMonitor::INotify(Thread * Self) {Thread::SpinAcquire(&_WaitSetLock, "WaitSet - notify"); | |
// 次要看这 DequeueWaiter 就是从 wiatset 里取出一个 线程 | |
// 怎么取?从头取 头是谁?第一个 wiat 的线程呗 看后边代码 | |
ObjectWaiter * iterator = DequeueWaiter(); | |
if (iterator != NULL) { | |
// 一堆代码 | |
// 这里须要留神 留神 留神 | |
//1. 如果 list 是空 就把 list 指向 iterator 也就是取出来那个元素 | |
// notify 的时候 是一个 走 if | |
if (list == NULL) { | |
iterator->_next = iterator->_prev = NULL; | |
_EntryList = iterator; | |
} else { | |
// 如果不是空 留神了 留神了 notifyAll 的时候 大多数会走这里 | |
iterator->TState = ObjectWaiter::TS_CXQ; | |
for (;;) { | |
// 这里不是很懂 _cxq 貌似是指向的_EntryList 的第一个元素 | |
ObjectWaiter * front = _cxq; | |
iterator->_next = front; | |
// cmpxchg : 如果 &_cxq 等于 front 就把 iterator 写到 &_cxq 内存 | |
// 这个操作时什么呢 把头上拿进去的 waiter 放到_EntryList 的首元素地位!!// 再品一下这句话 等会儿看 notifyAll 的时候 会用到这个常识 | |
if (Atomic::cmpxchg(&_cxq, front, iterator) == front) {break;} | |
} | |
} | |
iterator->wait_reenter_begin(this); | |
} | |
Thread::SpinRelease(&_WaitSetLock); | |
} |
inline ObjectWaiter* ObjectMonitor::DequeueWaiter() { | |
// dequeue the very first waiter | |
// 人家都正文了 取出第一个 waiter 这就是为什么 notify 是按 wait 程序来的 | |
ObjectWaiter* waiter = _WaitSet; | |
if (waiter) { | |
// 看这个办法 ↓ | |
DequeueSpecificWaiter(waiter); | |
} | |
return waiter; | |
} | |
inline void ObjectMonitor::DequeueSpecificWaiter(ObjectWaiter* node) { | |
// when the waiter has woken up because of interrupt, | |
// timeout or other spurious wake-up, dequeue the | |
// waiter from waiting list | |
ObjectWaiter* next = node->_next; | |
// 如果_next 等于本人 那阐明就一个 waiter | |
// 闭眼想:是不是一个的时候本人的_next 是指向本人的 老光棍都懂,左手拉右手。。if (next == node) {_WaitSet = NULL;// 因为就一个 拿出后把 waitset 置空} else { | |
// 这波操作 就是把头结点 毫无痕迹的取出来 | |
// 开端元素_next 指向 第二个元素 | |
// 第二个元素的_prev 指向开端元素 | |
ObjectWaiter* prev = node->_prev; | |
next->_prev = prev; | |
prev->_next = next; | |
// 把_WaitSet 援用到 next 也就是第二个元素上 第一个元素拜拜了您嘞 | |
if (_WaitSet == node) {_WaitSet = next;} | |
} | |
// _next _prev 置空 留一个 node 孤零零 给大家画一下看一眼 | |
node->_next = NULL; | |
node->_prev = NULL; | |
} |
取出前:
取出后:
3.1.3 hotspot notifyAll 代码(删减版)
// 这里代码简略 就是循环调用了 INotify() INotify 咱们曾经看过了 就是把 waiterset 的元素按程序取出来 | |
// 一个一个放到 EntryList 的头部 | |
// 看我下边图示意 | |
// The current implementation of notifyAll() transfers the waiters one-at-a-time | |
// from the waitset to the EntryList. This could be done more efficiently with a | |
// single bulk transfer but in practice it's not time-critical. Beware too, | |
// that in prepend-mode we invert the order of the waiters. Let's say that the | |
// waitset is "ABCD" and the EntryList is "XYZ". After a notifyAll() in prepend | |
// mode the waitset will be empty and the EntryList will be "DCBAXYZ". | |
void ObjectMonitor::notifyAll(TRAPS) {CHECK_OWNER(); // Throws IMSE if not owner. | |
if (_WaitSet == NULL) {return;} | |
DTRACE_MONITOR_PROBE(notifyAll, this, object(), THREAD); | |
int tally = 0; | |
while (_WaitSet != NULL) { | |
tally++; | |
INotify(THREAD); | |
} | |
OM_PERFDATA_OP(Notifications, inc(tally)); | |
} |
waiterset 的连线我就少画点儿 凑活看:
看出什么端倪没有?waiter 的程序 到了 EntryList 变成了 顺叙 这也是为什么 我测试的时候,多个 wait 在执行完 notifyAll 的时候 是倒着获取到锁的,还是那句话 JVM 没有强制规定规定,所以不能以这个为根据进行业务的编写
只是大略理解一下实现原理。。而已
public class WaitNotifyTest05 {public static void main(String[] args) throws InterruptedException {Object obj = new Object(); | |
for (int i = 0; i < 20; i++) {new Thread(()->{synchronized (obj){ | |
try {obj.wait(); | |
} catch (InterruptedException e) {e.printStackTrace(); | |
}finally {System.out.println(Thread.currentThread().getName()+"获取锁"); | |
} | |
} | |
},"线程名称"+i).start();} | |
TimeUnit.SECONDS.sleep(2); | |
new Thread(()->{synchronized (obj){ | |
try{System.out.println("notifyAll"); | |
} catch (Exception e) {System.out.println("异样了"); | |
} finally {obj.notifyAll(); | |
} | |
} | |
}).start();} | |
} | |
输入后果:notifyAll | |
线程名称 19 获取锁 | |
线程名称 18 获取锁 | |
线程名称 17 获取锁 | |
线程名称 16 获取锁 | |
线程名称 15 获取锁 | |
线程名称 14 获取锁 | |
线程名称 13 获取锁 | |
线程名称 8 获取锁 | |
线程名称 11 获取锁 | |
线程名称 10 获取锁 | |
线程名称 9 获取锁 | |
线程名称 12 获取锁 | |
线程名称 7 获取锁 | |
线程名称 6 获取锁 | |
线程名称 5 获取锁 | |
线程名称 4 获取锁 | |
线程名称 3 获取锁 | |
线程名称 2 获取锁 | |
线程名称 1 获取锁 | |
线程名称 0 获取锁 |
3.2 相干面试题
3.2.1 sleep 与 wait 的区别
- sleep 属于 Thread 类,wait 属于 Object 类
- sleep 暂停以后线程指定工夫,让出 CPU 然而不会开释锁
- wait 会开释锁 只有被调用 notify/notifyAll 的时候,才可能接着执行,这里肯定是可能,因为他不肯定能抢到锁
3.2.2 wait、notify 模仿生产者和消费者
public class WaitNotifyTest06 { | |
// 寄存生产数据的容器 | |
static LinkedList<String> list= new LinkedList<>(); | |
// 容器最大寄存数 | |
static int maxCount = 1; | |
public static void main(String[] args) { | |
// 生产者线程 | |
new Thread(()->{while (true) {synchronized (list){ | |
// 如果满了 wait 期待消费者生产了 告诉生产者 再生产 | |
if (list.size() == maxCount) { | |
try {list.wait(); | |
} catch (InterruptedException e) {e.printStackTrace(); | |
} | |
} | |
// 生产了元素 告诉消费者 接着生产 | |
String a = Double.valueOf(Math.random()*10000).intValue()+""; | |
list.add(a); | |
System.out.println("生产:"+a); | |
list.notify();} | |
} | |
},"生产者").start(); | |
// 消费者线程 | |
new Thread(()->{while (true) {synchronized (list){ | |
// 如果没有元素 wait | |
if (list.size() == 0) { | |
try{list.wait(); | |
} catch (Exception e) {e.printStackTrace(); | |
} | |
} | |
// 生产了 告诉生产者接着生产 | |
System.out.println("生产:"+list.pop()); | |
list.notify();} | |
} | |
},"消费者").start();} | |
} |
留神:大家看到我这里判断 list 的大小 用的是 if(lsit.size() == maxCount ) 和 if(list.size() > = 0)
一个生产者和一个消费者 貌似没什么大问题
那如果 2 个消费者呢?
public class WaitNotifyTest07 { | |
// 寄存生产数据的容器 | |
static LinkedList<String> list= new LinkedList<>(); | |
// 容器最大寄存数 | |
static int maxCount = 1; | |
public static void main(String[] args) { | |
// 生产者线程 | |
new Thread(()->{while (true) {synchronized (list){ | |
// 如果满了 wait 期待消费者生产了 告诉生产者 再生产 | |
if (list.size() == maxCount) { | |
try {list.wait(); | |
} catch (InterruptedException e) {e.printStackTrace(); | |
} | |
} | |
// 生产了元素 告诉消费者 接着生产 | |
String a = Double.valueOf(Math.random()*10000).intValue()+""; | |
list.add(a); | |
System.out.println("生产:"+a); | |
list.notify();} | |
} | |
},"生产者").start(); | |
// 消费者线程 | |
new Thread(()->{while (true) {synchronized (list){ | |
// 如果没有元素 wait | |
if (list.size() == 0) { | |
try{list.wait(); | |
} catch (Exception e) {e.printStackTrace(); | |
} | |
} | |
// 生产了 告诉生产者接着生产 | |
System.out.println("生产 01:"+list.pop()); | |
list.notify();} | |
} | |
},"消费者 01").start(); | |
// 消费者线程 | |
new Thread(()->{while (true) {synchronized (list){ | |
// 如果没有元素 wait | |
if (list.size() == 0) { | |
try{list.wait(); | |
} catch (Exception e) {e.printStackTrace(); | |
} | |
} | |
// 生产了 告诉生产者接着生产 | |
System.out.println("生产 02:"+list.pop()); | |
list.notify();} | |
} | |
},"消费者 02").start();} | |
} | |
异样喽:Exception in thread "消费者 02" java.util.NoSuchElementException | |
at java.util.LinkedList.removeFirst(LinkedList.java:270) | |
at java.util.LinkedList.pop(LinkedList.java:801) | |
at WaitNotifyTest07.lambda$main$2(WaitNotifyTest07.java:63) | |
at java.lang.Thread.run(Thread.java:748) |
为什么会这样呢?最容易想到的一条谬误门路是这样的:
- 生产者取得锁 生产“xxoo”
- 消费者 01 生产“xxoo”–> notify
- 这时候生产者 02 取得锁,判断 size() == 0 –>wait 开释锁
- 生产者取得锁,生产“xxxx”–> notify
- 消费者 01 取得锁 生产‘xxxx’–>notify
- 这是生产者 02 取得锁,然而这时候 size()==0 间接往下走 调用 pop 就会报错。
- 所以 个别咱们会用 while 代替 if,取得锁之后会再判断一下 wait 的条件,如果条件合乎再往下走
public class WaitNotifyTest07 { | |
// 寄存生产数据的容器 | |
static LinkedList<String> list= new LinkedList<>(); | |
// 容器最大寄存数 | |
static int maxCount = 1; | |
public static void main(String[] args) { | |
// 生产者线程 | |
new Thread(()->{while (true) {synchronized (list){ | |
// 如果满了 wait 期待消费者生产了 告诉生产者 再生产 | |
if (list.size() == maxCount) { | |
try {list.wait(); | |
} catch (InterruptedException e) {e.printStackTrace(); | |
} | |
} | |
// 生产了元素 告诉消费者 接着生产 | |
String a = Double.valueOf(Math.random()*10000).intValue()+""; | |
list.add(a); | |
System.out.println("生产:"+a); | |
list.notify();} | |
} | |
},"生产者").start(); | |
// 消费者线程 | |
new Thread(()->{while (true) {synchronized (list){ | |
// 如果没有元素 wait | |
while (list.size() == 0) { | |
try{list.wait(); | |
} catch (Exception e) {e.printStackTrace(); | |
} | |
} | |
// 生产了 告诉生产者接着生产 | |
System.out.println("生产:"+list.pop()); | |
list.notify();} | |
} | |
},"消费者").start(); | |
// 消费者线程 | |
new Thread(()->{while (true) {synchronized (list){ | |
// 如果没有元素 wait | |
while (list.size() == 0) { | |
try{list.wait(); | |
} catch (Exception e) {e.printStackTrace(); | |
} | |
} | |
// 生产了 告诉生产者接着生产 | |
System.out.println("生产 02:"+list.pop()); | |
list.notify();} | |
} | |
},"消费者 02").start();} | |
} |
咦?又有问题了。死锁了!!
因为一个生产者,两个消费者 须要用 notifyAll 代替 notify
为什么 notify 会死锁?轻易举例一种状况
- 生产者取得锁 生产“xxoo”,而后生产者又抢到锁 size() == 1 –> wait() 了
- 消费者 01 抢到锁 生产 xxoo 而后本人又抢到锁 size() == 0 — > wait()
- 这时候消费者 02 抢到锁 size() ==0 也 wai()了 都 wait 了
public class WaitNotifyTest07 { | |
// 寄存生产数据的容器 | |
static LinkedList<String> list= new LinkedList<>(); | |
// 容器最大寄存数 | |
static int maxCount = 1; | |
public static void main(String[] args) { | |
// 生产者线程 | |
new Thread(()->{while (true) {synchronized (list){ | |
// 如果满了 wait 期待消费者生产了 告诉生产者 再生产 | |
if (list.size() == maxCount) { | |
try {list.wait(); | |
} catch (InterruptedException e) {e.printStackTrace(); | |
} | |
} | |
// 生产了元素 告诉消费者 接着生产 | |
String a = Double.valueOf(Math.random()*10000).intValue()+""; | |
list.add(a); | |
System.out.println("生产:"+a); | |
list.notifyAll();} | |
} | |
},"生产者").start(); | |
// 消费者线程 | |
new Thread(()->{while (true) {synchronized (list){ | |
// 如果没有元素 wait | |
while (list.size() == 0) { | |
try{list.wait(); | |
} catch (Exception e) {e.printStackTrace(); | |
} | |
} | |
// 生产了 告诉生产者接着生产 | |
System.out.println("生产:"+list.pop()); | |
list.notifyAll();} | |
} | |
},"消费者").start(); | |
// 消费者线程 | |
new Thread(()->{while (true) {synchronized (list){ | |
// 如果没有元素 wait | |
while (list.size() == 0) { | |
try{list.wait(); | |
} catch (Exception e) {e.printStackTrace(); | |
} | |
} | |
// 生产了 告诉生产者接着生产 | |
System.out.println("生产 02:"+list.pop()); | |
list.notifyAll();} | |
} | |
},"消费者 02").start();} | |
} |
最初附上本人公众号刚开始写 愿一起提高:
留神:以上文字 仅代表个人观点,仅供参考,如有问题还请指出,立刻马上连滚带爬的从被窝里进去改过。