疑惑:getTask()方法中判断 queue.isEmpty()的时候为什么一定要用 while 而不是 if 呢?
代码如下:
class TaskQueue {
Queue<String> queue = new LinkedList<>();
public synchronized void addTask(String s) {
this.queue.add(s);
this.notifyAll();
}
public synchronized String getTask() throws InterruptedException {
while (queue.isEmpty()) {
this.wait();
}
return queue.remove();
}
}
教程中的解释如下,但是自己也没理解明白。 “if 的写法实际上是错误的,因为线程被唤醒时,需要再次获取 this 锁。多个线程被唤醒后,只有一个线程能获取 this 锁,此刻,该线程执行 queue.remove()可以获取到队列的元素,然而,剩下的线程如果获取 this 锁后执行 queue.remove(),此刻队列可能已经没有任何元素了,所以,要始终在 while 循环中 wait(),并且每次被唤醒后拿到 this 锁就必须再次判断”
新手学习 java 线程通信,还请大佬们指导指导。
1
xx6412223 309 天前 1
1. object wait 会释放锁,也就是可能有多个线程在 this.wait()等待唤醒。而 while 会某个线程被唤醒后会再次检查 queue.isEmpty(),而 if 会直接向下运行。
2. 更易读的方式是使用 ConcurrentLinkedQueue |
2
Znemo 309 天前 1
两个线程调用 `getTask` 的场景,如果使用 if ,其中一个线程会消费掉队列中的数据,接着第二个线程会在 wait 处被唤醒,继续向下执行,错误地调用 `queue.remove()`
|
3
vagusss 309 天前 1
用 if,线程被唤起后, 不会再次判断条件
用 while,线程被唤起后, 会再次判断条件 |
5
falsemask 309 天前 1
可能存在虚假唤醒,可以参考一下这个知乎回答 https://www.zhihu.com/question/271521213
|
6
ZZMine OP 好的明白了,感谢大家~ 主要是 this.wait()被唤醒后还是继续执行的,而不是把方法再重新执行。
|
7
9c04C5dO01Sw5DNL 309 天前
有两个原因,展开讲一下第一个原因。
这个例子中有两个关于锁的队列,一个是 CLH 锁队列,即:获取和释放锁时的队列。还有一个是条件队列,即 notify/wait/signal/await 这种。 在需要所但是没有获取到锁的时候,线程进入锁队列。当线程获取到锁又 wait/await 的时候,它会做两个动作 1. 释放锁 2. 线程转移到条件队列,不再在所队列中。 这个例子中会出现问题: 1. 在初始队列为空,假设当生产线程添加 1 个元素、notify 并退出同步代码块之后,那么会有多个消费者线程从条件队列转移到了锁队列中,并且有一个消费者线程获取到了锁。 2. 如果不用 while ,而是用 if ,那么当获取到锁的消费者线程消费完,队列为空,此时消费者释放锁,这会导致其他消费者线程重新竞争锁。因为它们现在是在锁队列中,而不是在条件队列中。不幸的是,现在队列中唯一的元素已经被消费了。 3. 用 while 就不一样了,用 while ,虽然有多个消费者线程重新竞争锁,并且有一个竞争成功,但是它在判断队列为空之后,又会因为 await/wait 进入条件队列 |
8
gaifanking 309 天前
就是解决伪唤醒问题,一方面操心系统都有几率出 bug 唤醒你,另一方面从业务开发来讲我们经常使用 notifyAll 而不是 notify ,这时多个排队的线程都会被唤醒的,但只能有一个去跑。
|
9
hapeman 308 天前 1
两个消费者线程先后执行 getTask(),此时队列为空,两个消费者线程执行 wait()进行休眠
之后 一个生产者线程执行 addTask()向消费队列添加了一个任务(队列长度为 1 )并通过 notifyAll()唤醒了所有消费者线程,此时如果是 if 判断,消费者线程 1 获取到锁并执行了 remove()后释放锁,,由于是 if 语句 消费者线程 2 此时会等待线程 1 释放锁后继续执行下面的语句,而此时队列已经为空了去调用 remove()方法会抛出异常;而改用 while 语句消费者线程 2 获取到锁之后仍会进去 while 循环判断队列是否为空,调用 wait()方法 可以看一下 https://cloud.tencent.com/developer/article/2281627 中生产者消费者模块提到了这个问题-虚假唤醒 |