java中的wait()-notify()机制以一种奇怪的方式出现故障

java中的wait()-notify()机制以一种奇怪的方式出现故障,java,multithreading,synchronization,producer-consumer,Java,Multithreading,Synchronization,Producer Consumer,我试着在这里阅读一些类似问题的答案(我总是这样做),但没有找到(或不理解?)这个特定问题的答案 我正在实现一个相当简单的consumer-producer类,它从不同的线程接收元素到列表中,并重复使用它们。该类具有以下代码: public class ProduceConsume implements Runnable { LinkedList<Integer> _list = new LinkedList<Integer>(); public syn

我试着在这里阅读一些类似问题的答案(我总是这样做),但没有找到(或不理解?)这个特定问题的答案

我正在实现一个相当简单的consumer-producer类,它从不同的线程接收元素到列表中,并重复使用它们。该类具有以下代码:

public class ProduceConsume implements Runnable
{

    LinkedList<Integer> _list = new LinkedList<Integer>();

    public synchronized void produce(Integer i)
    {
        _list.add(i);
        notify();
    }

    public void run()
    {
        while(true)
        {
            Integer i = consume();

            // Do something with the integer...
        }
    }

    private synchronized Integer consume()
    {
        if(_list.size() == 0)
        {
            try
            {
                wait();
            }
            catch(InterruptedException e){}

            return _list.poll();
        }
    }
}
列表仍然为空。我无法控制自己的情绪——我是不是做错了什么?重复尝试轮询检测零长度列表的可运行线程不应该等待,并且只有在生产者方法完成后才被唤醒,从而使列表非空吗

除了对生成的调用之外,没有其他东西从外部“接触”类。runnable类上没有同步其他线程

顺便说一句,出于几个原因,我希望使用自己的变体,而不是CopyOnWriteArrayList等类

谢谢!任何帮助都将不胜感激


另外,我没有多次使用等待通知,但在过去,当我使用等待通知时,它起了作用。如果我犯了愚蠢的错误,我道歉

由于等待释放了锁,您无法根据开始等待前测试的条件进行推理,因此假设等待退出后条件必须已更改是无效的。您需要在循环中调用wait,这样一旦线程停止等待并再次获取锁,它就会检查等待的条件是否具有预期值:

private synchronized Integer consume()
{
    try {
        while (_list.size() == 0) {
            wait();
        }            
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    return _list.poll();
}
发件人:

注意:始终在测试等待条件的循环中调用wait

而且,仅仅因为wait返回了一个通知,就假定某个东西发送了一个通知,这也是不安全的。即使没有通知(虚假唤醒),等待也会返回

如果没有一个完整的工作示例,很难说是什么导致了你所看到的

链接的Oracle教程页面有一个生产者-消费者示例,您可能想看看。

作为各州的Javadoc

与单参数版本一样,中断和虚假唤醒是可能的,并且此方法应始终在循环中使用:


向我们展示如何使用
ProduceConsume
实例。感谢您的回复-该程序在ProduceConsume类之外非常复杂,但它的使用非常简单-我可以将其简化为另一个线程(例如主线程),它有一个无限循环,在每次迭代时暂停N毫秒,并发送一个随机数以产生消费。阅读该问题的所有投票结果。这种情况被称为“虚假唤醒”,这是众所周知的。我想你不能只使用阻塞队列?感觉很新鲜,也许你一直都有这种感觉。也就是说,愿你永不停止学习。你停止在这个行业学习的那一天将是你职业生涯结束的开始。我已经开发软件35年了,但我仍然觉得自己是新手。所以你认为这是一次虚假的觉醒?@SotiriosDelimanolis这就是为什么我写这个答案的原因,如果这是你的意思的话。我想这是偶尔醒来的最可能的解释,尽管那里什么都没有。谢谢你的回复。我只是忘记在InterruptedException中编写异常处理-如果发生这种情况,我的代码将打印e.printStackTrace()。我只是忘了在这里写。未打印堆栈跟踪:-)@amirkr正如您所说,除非实际添加了某些内容,否则无法调用list.poll()。谢谢。我将把条件转换为while循环。问题是,正如我在上面所写的,在没有填写列表的情况下,线程被唤醒真的很奇怪。没有其他线程(我的线程除外)可以从它的wait()中唤醒ProduceConsume,因此理论上它有点奇怪。但是,嘿,如果它能工作…@amirkr问题是,底层系统调用不能保证在没有调用notify()时不会唤醒它。正如我所说,由于列表是空的,制作人在列表中插入了一个数字,所以编码会发生变化,这很奇怪,因为列表现在不是空的(eanwhile中没有发生任何事情,我的代码没有)-线程唤醒并继续。在99.99%的情况下,它是有效的。但有时,情况并非如此。希望循环的东西能改变它(我仍然觉得它很奇怪,但是如果它能工作的话,我会很好的:)@PeterLawrey-这可以解释它!谢谢@amirkr:如果没有完整的代码示例,很难准确地说出来。while循环肯定会有帮助,因为它会检查对象的状态。
private synchronized Integer consume()
{
    try {
        while (_list.size() == 0) {
            wait();
        }            
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
    return _list.poll();
}
 synchronized (obj) {
     while (<condition does not hold>)
         obj.wait();
     ... // Perform action appropriate to condition
 }
private synchronized Integer consume() {
    try {
        while (_list.isEmpty()) 
            wait();
        return _list.poll();
    } catch(InterruptedException e) {
        throw new IllegalStateException("Interrupted");
    }
}