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

2022-08-31 06:14:29

我能否获得一个完整的简单场景,即建议如何使用它的教程,特别是队列?


答案 1

和 方法旨在提供一种机制,允许线程阻塞,直到满足特定条件。为此,我假设您要编写一个阻塞队列实现,其中您有一些固定大小的元素支持存储。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 语句。这是等待线程有时可以在不被调用的情况下重新激活的地方。将此检查放入 while 循环中将确保如果发生虚假唤醒,将重新检查条件,并且线程将再次调用。notify()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();
        }
    }
}

当然,如果您确实需要阻塞队列,那么您应该使用BlocktingQueue接口的实现。

另外,对于这样的东西,我强烈推荐Java并发在实践中,因为它涵盖了你可能想知道的与并发相关的问题和解决方案的所有内容。


答案 2

不是队列示例,而是非常简单:)

class MyHouse {
    private boolean pizzaArrived = false;

    public void eatPizza(){
        synchronized(this){
            while(!pizzaArrived){
                wait();
            }
        }
        System.out.println("yumyum..");
    }

    public void pizzaGuy(){
        synchronized(this){
             this.pizzaArrived = true;
             notifyAll();
        }
    }
}

一些要点:
1)永远不要做

 if(!pizzaArrived){
     wait();
 }

始终使用 while(条件),因为

  • a) 线程可以偶尔从等待状态中唤醒,而不会被任何人通知。(即使披萨小哥没有敲响铃声,也会有人决定尝试吃披萨。
  • b) 获取同步锁后,应再次检查条件。比方说披萨不会永远持续下去。你醒了,排队吃披萨,但这对每个人来说还不够。如果你不检查,你可能会吃纸!:)(也许更好的例子是。while(!pizzaExists){ wait(); }

2) 在调用 wait/nofity 之前,您必须保持锁定(已同步)。线程还必须在唤醒之前获取锁定。

3)尽量避免在同步块中获取任何锁,并努力不调用外星方法(您不确定它们在做什么的方法)。如果有必要,请务必采取措施避免死锁。

4) 小心通知()。坚持使用 notifyAll() 直到你知道自己在做什么。

5)最后,但并非最不重要的一点是,阅读Java并发实践