Wait() 和 Notify() 概念 - Java 多线程

3
class Q {
  volatile boolean valueSet = false;
  volatile int n;

  synchronized int get () {
    if ( !valueSet ) {
      try {
        wait();
      } catch ( InterruptedException e ) {
        System.out.println( "InterruptedException caught" );
      }
    }

    System.out.println( "Got: " + n );
    valueSet = false;
    notify();
    return n;
  }

  synchronized void put ( int n ) {
    if ( valueSet ) {
      try {
        wait();
      } catch ( InterruptedException e ) {
        System.out.println( "InterruptedException caught" );
      }
    }

    this.n = n;
    valueSet = true;
    System.out.println( "Put: " + n );
    notify();
  }
}

class Producer
    implements Runnable {
  Q q;
  Producer ( Q q ) {
    this.q = q;
    new Thread( this, "Producer" ).start();
  }

  public void run () {
    int i = 0;
    while ( true ) {
      q.put( i++ );
    }
  }

}

class Consumer
    implements Runnable {
  Q q;
  Consumer ( Q q ) {
    this.q = q;
    new Thread( this, "Consumer" ).start();
  }

  public void run () {
    while ( true ) {
      q.get();
    }
  }

}

class PCFixed {
  public static void main ( String args[] ) {
    Q q = new Q();
    new Producer( q );
    new Consumer( q );
    System.out.println( "Press Control-C to stop." );
  }
}

我无法理解这是如何运作的。以这个流程为例:生产者进入put方法并调用notify()。如果消费者还没有调用wait()该怎么办?此外,一旦生产者调用了notify(),当生产者还没有放弃监视器时,消费者怎么能进入get()方法呢?请帮我解答。
3个回答

2
我假设顶部的类是Q,它缺少一些代码。无论如何,总体思想是布尔值valueSetwait()/notify()方法配合使用。
如果消费者已经开始等待,他通过同步的get()方法获得了对Q实例的锁定,然后在等待时释放它。
如果消费者尚未开始等待,则生产者可能已经拥有对Q实例的锁定,因为put()方法在同一个锁上同步。一旦生产者退出锁定,他将调用notify()以及将valueSet布尔值设置为true。
消费者的下一个get()调用将在尝试等待之前读取布尔值,并注意到有东西存在,获取n的内容并执行所需的工作。如果值未被设置,这意味着在消费者离开期间没有收到任何内容,则它将在锁上wait()以获取新工作,下一个notify()将唤醒它。
更新
在您在评论中提到的情况中,实际上是在相反的情况下询问完全相同的情况。相同的逻辑适用。
消费者当前正在等待,生产者调用notify()。生产者当前持有锁,并将在方法的持续时间内继续保持锁定。 notify()所做的只是让另一个当前正在等待锁的线程知道,当释放锁时,它可以尝试获取锁并恢复执行。在这种情况下,只有另一个线程,但如果有多个线程,则仅选择一个(要唤醒所有人,应调用notifyAll())。
1.生产者退出该方法,释放锁。 2.消费者醒来并获得锁。
此时,不清楚生产者是否已经到达并且当前正在等待锁定,或者它尚未进入该方法。布尔标志和wait()/notify()方法的相同配合也适用于此。
在消费者通过退出方法释放锁之前,它将同时将布尔标志设置为false并调用notify()
如果生产者当前已经在等待锁,则调用notify()将让它知道它可以在释放锁时唤醒并继续进行。
如果生产者没有在wait()调用中等待,那么它必须在方法外部(可能正在等待进入该方法并以这种方式获取锁)。当消费者退出该方法并释放锁时,生产者获取锁并检查布尔标志。它被设置为false,因此生产者不会尝试调用wait(),只是放下其值,设置布尔标志并调用notify()

采取以下流程。消费者首先进入get方法。它调用wait并释放监视器。现在生产者进入get方法,然后调用notify()。一旦调用了notify,它是否完成了方法的执行并释放了监视器?如果是,则消费者调用notify()。但是生产者还没有调用wait()吗?现在会发生什么? - Cafecorridor
@Cafecorridor,我已经更新了我的答案,现在包括对此的回答。 - Chris Hannon

1
  1. 如果消费者尚未调用wait(),会发生什么?
    • 消息将丢失
  2. 一旦生产者调用notify(),当生产者尚未释放监视器时,消费者如何进入get()方法?
    • 它将死锁-阻塞直到监视器被释放。

那么应该有一个块,即程序不应继续执行。为什么它会打印出正确的输出? - Cafecorridor

0

我会尝试理解这是如何工作的。例如,Producer 已经进入了 put() 方法并看到监视器是空闲的,他可以执行放置操作。在他执行任务的时候,Consumer 来了并尝试执行 get() 操作。但是他失败了,因为监视器已经被 Producer 占用了,所以他调用了 wait(),意味着他被阻塞在那行代码上,等待其他线程调用 notify()。这里的诀窍是,在他再次被通知监视器再次空闲之前,他永远不会再次调用 get(),也就是说,他不会再执行任何空循环。因此,当 Producer 完成工作后,他调用 notify()Consumer 就会从他离开的地方继续执行。我知道一开始可能有点难以理解,而且也有点难以解释,但是当你理解了之后,你会发现这些东西真的很简单而且很强大。祝你好运!


1
当生产者线程已经锁定监视器时,消费者如何调用wait()函数? - Cafecorridor
@Cafecorridor,调用wait()只是定义线程进入代码的关键部分的意图,它并不尝试执行它。通过调用wait(),线程开始等待另一个线程的notify()调用以恢复其操作,在此之前它被阻塞。 - Egor

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接