在java中使用wait()和notify()的简单方案

Ola*_*eni 175 java notify wait

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

Jar*_*ell 267

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

关于必须使用等待和通知机制的方式,有几点需要注意.

首先,需要确保任何呼叫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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

此外,对于这样的东西,我强烈推荐实践中的Java Concurrency,因为它涵盖了您可能想要了解的有关并发相关问题和解决方案的所有内容.

  • @ Brut3Forc3你需要读取wait()的javadoc:它说:*线程释放了这个监视器*的所有权.因此,只要调用wait(),就会释放监视器,另一个线程可以执行队列的另一个同步方法. (10认同)
  • @greuze,`notify`只唤醒一个线程.如果两个消费者线程竞争移除一个元素,一个通知可能会唤醒另一个消费者线程,它不能对它做任何事情并将重新进入休眠状态(而不是生产者,我们希望它会插入一个新元素.)因为生产者线程没有被唤醒,没有插入任何内容,现在所有三个线程都将无限期地休眠.我删除了之前的评论,因为它(错误地)说虚假的唤醒是问题的原因(事实并非如此). (7认同)
  • @finnw 据我所知,您发现的问题可以通过使用 notifyAll() 来解决。我对吗? (2认同)
  • @Brut3Forc3 重新阅读这篇文章。此时,Jared 解释了如果方法不同步会发生什么情况。 (2认同)

Enn*_*oji 147

不是队列的例子,但非常简单:)

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

 if(!pizzaArrived){
     wait();
 }
Run Code Online (Sandbox Code Playgroud)

总是使用while(条件),因为

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

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

3)尽量避免在同步块中获取任何锁定,并努力不调用外来方法(您不知道他们正在做什么的方法).如果必须,请务必采取措施避免死锁.

4)注意notify().坚持使用notifyAll()直到你知道自己在做什么.

5)最后,但并非最不重要的,阅读Java Concurrency in Practice!

  • @Everyone:添加了一些解释.HTH. (2认同)
  • 我不明白——线程1执行eatPizza()方法并进入顶部同步块,并在MyHouse类上同步。比萨饼还没有到,所以它只是等待。现在线程 2 尝试通过调用pizzaGuy() 方法来交付比萨;但不能因为线程 1 已经拥有锁并且它没有放弃它(它一直在等待)。结果实际上是一个死锁——线程 1 正在等待线程 2 执行 notifyAll() 方法,而线程 2 正在等待线程 1 放弃对 MyHouse 类的锁定......我错过了什么这里? (2认同)

pol*_*nts 35

即使你要求wait()notify()具体而言,我觉得这句话就够仍然很重要:

Josh Bloch,Effective Java第2版,第69项:首选并发实用程序waitnotify(强调他的):

鉴于使用waitnotify正确使用的难度,您应该使用更高级别的并发实用程序来代替使用,wait并且notify直接就像"并发汇编语言"中的编程一样,与提供的更高级语言相比java.util.concurrent.很少,如果有的话,在新代码中使用wait和使用notify.


ext*_*eon 6

你看过这个Java教程了吗?

此外,我建议你不要在真实软件中使用这种东西.玩它很好,所以你知道它是什么,但并发性在整个地方都有陷阱.如果要为其他人构建软件,最好使用更高级别的抽象和同步集合或JMS队列.

至少这是我的工作.我不是一个并发专家,所以我尽可能远离手工处理线程.