调用 wait() 后 return 一个值是什么意思?

What does it mean to return a value after calling wait()?

在下面的代码中,我有一个关于调用 wait() 后会发生什么的问题。在我的代码中,我在调用 wait() 后 returning 一个值,这实际上是做什么的?我认为调用 wait() 会暂停当前线程,但是如果调用 wait() 时没有 return 为假,那么传递给 addWorkItem(Integer i) 的值 i 会发生什么?您可以在生产者线程中看到,如果无法将其添加到双端队列,它会将 i 添加到重试缓冲区。如果我在等待后不 return false,值 i 会丢失,还是线程唤醒后它仍然存在?

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;


public class ConsumerProducer2 {

    private static int QUEUE_SIZE = 10;

    private Deque<Integer> queue = new ArrayDeque<Integer>(QUEUE_SIZE);


    public synchronized boolean addWorkItem(Integer i) {
        while (queue.size() >= QUEUE_SIZE) {
            try {
                wait();
                return false; // WHAT HAPPENS HERE?
            } catch (InterruptedException ex) {}
        }

        queue.addLast(i);
        notify();
        return true;
    }

    public synchronized Integer getWork() {
        while (queue.size() == 0) {
            try {
                wait();
                return null;  // WHAT HAPPENS HERE?
            } catch (InterruptedException ex) {
            }
        }
        Integer i = queue.removeFirst();
        notify();
        return i;
    }

    public static void main(String[] args) {
        new ConsumerProducer2().go();
    }

    public void go() {
        ConsumerThread ct = new ConsumerThread();
        ct.start();
        ConsumerThread ct2 = new ConsumerThread();
        ct2.start();
        ProducerThread pt = new ProducerThread();
        pt.start();
    }

    class ConsumerThread extends Thread {
        public void run() {
            while(true) {

                Integer work = getWork();
                if (work == null) {
                } else {
                    System.out.println("Thread: " + this.getId() + " received work: " + work);
                }
            }
        }
    }

    class ProducerThread extends Thread {
        private List<Integer> retryList = new ArrayList<Integer>();
        public void run() {
            while(true) {
                Integer currWork;
                if (retryList.size() == 0) {
                    currWork = (int) (Math.random() * 100);
                } else {
                    currWork = retryList.remove(0);
                    System.out.println("Thread: " + this.getId() + " retrying old work: " + currWork);
                }
                if (!addWorkItem(currWork)) {
                    System.out.println("Thread: " + this.getId() + " could not add work (because buffer is probably full): " + currWork);
                    retryList.add(currWork);
                } else {
                    System.out.println("Thread: " + this.getId() + " added work to queue: " + currWork);
                }
            }
        }
    }
}

看看this

小故事:一个等待的线程可以被另一个调用通知的线程唤醒。因此,在您的情况下,addWorkItem 将在另一个线程调用 notify().

之后调用 wait() 的线程中 return false

另外看看你的逻辑我认为你试图在队列为空时阻止消费者并在有工作要做时唤醒它。 并且您希望生产者在队列为空之前不要交付新作业。 如果是这种情况,那么在等待之后调用 return 只会关闭您的 consumer/producer,而不是让他们尽可能地完成工作。

让生产者维护一个重试缓冲区确实可以防止 i 值丢失,但这仍然不是编写方法的好方法。

从 while 循环内部返回没有意义。您检查队列的大小,如果它已达到最大值,您将等待直到收到队列大小更改的通知,然后莫名其妙地 return false (??)。等待并没有真正完成任何事情。

在 addWorkItem 中等待的目的是延迟您的线程,直到队列有空间容纳新值。你应该在一个循环中等待,当你从等待中出来时,你的线程重新获取锁和 re-checks 条件(队列大小>最大)以查看它是否可以添加项目。

一旦线程退出 while 循环,它就会持有锁,队列中肯定有足够的空间容纳新项目(因为没有其他线程可以做任何事情来改变队列的大小,而这时线程持有锁),它可以继续将值添加到队列中。

您正在以一种非生产性的方式捕获 InterruptedException,因为您捕获了它,不用费心去恢复中断标志,而是返回到 while 循环的顶部。您应该使用中断来退出等待并退出该方法。在这里抛出 InterruptedException 会更有意义;线程 运行 方法应该比这个对象更了解如何处理中断。

您不应该假设仅当线程收到通知时才等待 return 秒,它可以 return 无需通知。这就是在循环中调用 wait 的原因之一。

修改后的版本:

public synchronized boolean addWorkItem(Integer i) throws InterruptedException {
    while (queue.size() >= QUEUE_SIZE) {
        wait();
    }
    queue.addLast(i);
    notify();
    return true;
}

如果你想找个借口 return false 如果队列在某个时间范围内没有为新条目腾出空间(超时),你可以使方法 return false在很多 real-life 情况下可能是一件好事):

public synchronized boolean addWorkItem(Integer i) throws InterruptedException {
    final long maxWaitTime = 60L * 1000;
    long totalWaitTime = 0;
    while (queue.size() >= QUEUE_SIZE && totalWaitTime <= maxWaitTime) {
        long waitStartTime = System.currentTimeMillis();
        wait(maxWaitTime);
        totalWaitTime += (System.currentTimeMillis() - waitStartTime);
    }
    if (queue.size() >= QUEUE_SIZE) {
        return false;
    }
    queue.addLast(i);
    notify();
    return true;
}

这仍将使用重试缓冲区(上面的第一个版本根本不会这样做),但可能不会像现在那么多。

另一件事:您有生产者线程和消费者线程同时访问它,并且在这两种情况下都会调用通知。由于 notify 只唤醒一个线程,一个线程有可能得到一个与它无关的通知(所以被通知的线程醒来,检查它的条件并发现它仍然是 false,然后再等待一段时间,而另一个线程通知实际上很重要,永远不会发现它)。有不同的方法可以解决问题,你可以

  • 分配单独的锁,一把给生产者,一把给消费者,

  • 减少传递给 wait 方法的超时时间,这样您就不太依赖于收到通知,或者

  • 您可以使用 notifyAll(性能较差但可以快速修复)。