当前位置: 首页 > 面试题库 >

在Java中使用wait()和notify()的简单场景

阎弘雅
2023-03-14
问题内容

我可以得到一个完整的简单方案,即建议如何使用它的教程,特别是在队列中吗?


问题答案:

wait()notify()方法被设计为提供一种机制,以允许一个线程块,直到一个特定的条件被满足。为此,我假设你要编写一个阻塞队列实现,其中具有一些固定大小的元素后备存储。

你要做的第一件事是确定你希望方法等待的条件。在这种情况下,你将希望该put()方法阻塞直到存储空间可用,并且你将希望该take()方法阻塞直到返回某些元素。

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void put(T element) throws InterruptedException {
        while(queue.size() == capacity) {
            wait();
        }

        queue.add(element);
        notify(); // notifyAll() for multiple producer/consumer threads
    }

    public synchronized T take() throws InterruptedException {
        while(queue.isEmpty()) {
            wait();
        }

        T item = queue.remove();
        notify(); // notifyAll() for multiple producer/consumer threads
        return item;
    }
}

关于必须使用等待和通知机制的方式,需要注意一些事项。

首先,你需要确保对代码的任何调用wait()notify()在代码的同步区域内(并且wait()notify()调用在同一对象上同步)。造成这种情况的原因(除了标准线程安全问题之外)是由于某种原因导致的信号丢失。

这样的一个示例是,put()当队列碰巧已满时,线程可能会调用,然后它检查条件,发现队列已满,但是在它可以阻止另一个线程调度之前。然后,第二个线程take()是队列中的一个元素,并通知等待线程该队列不再满。但是,由于第一个线程已经检查了条件,因此wait()即使可以进行进度,它也将在重新调度后简单地进行调用。

通过在共享库上同步,可以确保不会发生此问题,因为在第take()一个线程实际被阻塞之前,第二个线程的调用将无法进行。

其次,由于称为虚假唤醒的问题,你需要将要检查的条件放入while循环中,而不是if语句中。在这里有时可以在不notify()调用等待线程的情况下重新激活它。将此检查置于while循环中将确保如果发生虚假唤醒,将重新检查条件,并且线程将wait()再次调用。

就像其他答案中提到的那样,Java 1.5引入了一个新的并发库(在java.util.concurrent包中),该库旨在在等待/通知机制上提供更高级别的抽象。使用这些新功能,你可以像这样重写原始示例:

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public void put(T element) throws InterruptedException {
        lock.lock();
        try {
            while(queue.size() == capacity) {
                notFull.await();
            }

            queue.add(element);
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while(queue.isEmpty()) {
                notEmpty.await();
            }

            T item = queue.remove();
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

当然,如果你实际上需要阻塞队列,则应该使用BlockingQueue接口的实现 。

另外,对于这种事情,我强烈建议在实践中使用Java Concurrency,因为它涵盖了你可能希望了解的与并发相关的问题和解决方案的所有内容。



 类似资料:
  • 我正在学习 处等待(由于< code > synchronized ,它从未进入其代码块)。一旦生产者线程退出它的同步代码块,消费者线程将进入它的。现在,队列是非空的,因为生产者只是在通知之前放了一些东西进去。消费者线程将移除它,调用notify,退出它的块,此时生产者将获得锁,因为它现在已经在生产者函数中的< code>synchronized(lock)行等待。三个问题: > < li> 在我

  • 我有个问题。当我在synchronized块中使用时,我有IllegalMonitorStateException。有谁能帮我解决这个问题吗? 我必须这样做,一个线程将发送到第二个线程char,然后这个线程必须等待和第二个线程打印这个char。在第二个线程等待之后,第一个线程再次发送下一个字符 main.java:

  • 问题内容: 如上面的示例,如果先进入块,则ThreadB中的后续块将告诉主线程继续。 但是我们不能保证将在)之前执行,如果ThreadB首先进入该块怎么办?会在之前执行,所以会永远挂在那里(因为不再告诉它继续执行)?通常有什么合适的方法来解决这个问题? 问题答案: 您几乎应该总是将谓词与等待/通知一起使用。也就是说,您需要可以检查的条件,例如变量变为true,队列变为空/满等。仅盲目地等待某人调用

  • 问题内容: 我有2个矩阵,我需要将它们相乘,然后打印每个单元格的结果。准备好一个单元格后,我就需要打印它,但是例如,即使[2] [0]的结果先准备好,我也需要在单元格[2] [0]之前打印[0] [0]单元格。所以我需要按顺序打印它。因此,我的想法是让打印机线程等待,直到通知它准备打印正确的单元格,然后它将打印该单元格并返回等待状态,依此类推。 所以我有这个线程做乘法: 打印每个单元格结果的线程:

  • 我发现了Java并发的奇怪行为。请参阅下面的下一段代码: 在我看来,这段代码应该挂起并永远等待,但是在控制台中的next out没有任何问题地完成了代码: 我试图找到一些关于如果线程死了通知锁的信息,但缺乏。我也没有在Java规范中找到任何信息。 但是如果我试图锁定其他对象(而不是thread对象),就会像我预期的那样工作得很好。

  • 问题内容: 似乎该线程都在其他线程调用或在此线程上唤醒。两者之间有什么区别吗? - 编辑 - 我知道一个是通知对象,另一个是中断线程。但是,这两种情况都会导致相同的结果,也就是说,该线程被唤醒,所以我想问的是这两种情况的后果是如何不同的。 问题答案: 当线程在某个监视器上调用notify时,它将唤醒在该监视器上等待的单个线程,但是 哪个 线程被唤醒由调度程序决定。(或者,一个线程可以调用notif