3

据一位同事介绍,JVM 不保证当对一个对象调用“notify”时,那个时候会通知正确的“wait”。他说,可能存在一种情况,即先前不再有效的通知在无效时间交付。

这是真的?如果是这样,这是如何/为什么会这样,如果你不能假设这样基本的东西会起作用,等待/通知机制有什么用?

4

3 回答 3

2

对于 java.lang.Object.notify,Javadoc 说:

唤醒正在此对象的监视器上等待的单个线程。如果有任何线程正在等待该对象,则选择其中一个被唤醒。该选择是任意的,并由实施自行决定。线程通过调用其中一个等待方法在对象的监视器上等待。

这是等待特定条件的模式:

synchronized( lock ) {
   while( conditionEvaluation( data )) {
      lock.wait();
   }
}

对方应使用java.lang.Object.notifyAll()以确保应用程序的活力。即使今天只有一个服务员,经过软件的多次演进,未来可能是几个服务员,所以notifyAll()notify().

于 2012-12-07T07:40:01.043 回答
2

每个等待内在锁的对象都将进入锁的等待集。当您在锁对象上调用 notify 时,将选择其等待集中的线程之一来恢复工作。JVM 提供的唯一保证是等待的线程最终将通知。这种非确定性行为的主要原因之一是 JVM 选择挂起线程运行的方式,这是任意的。然而,此外,java 中的锁实现了允许线程插入的非公平锁定策略。这仅仅意味着允许新的锁请求跳到锁的等待集之前,如果锁在请求时可用。这背后的理由是,鉴于存在大量争用,在选择和恢复等待集中的挂起线程及其实际运行时间之前,可能会有一些(潜在的重大)延迟。因此,来自线程的任何传入锁请求都可以利用此时间延迟立即运行,希望在恢复的线程准备好运行时它已经释放了锁。

  1. 先前已获取监视器 X 的线程 A 调用 notify()
  2. 在监视器 X 上等待的线程 B 已选择(任意)挂起。
  3. 线程 C 尝试获取监视器 X,发现它可用并获取它。
  4. 线程 C 运行(尽管线程 B 目前正在恢复过程中)
  5. 线程 C 完成执行并释放监视器 X,就在线程 B 实际运行之前。
  6. 线程 B 已准备好运行,因此它获取锁并开始执行。

很明显,在第 2 步和第 6 步之间存在一些时间间隔,其中没有线程实际使用锁。线程 C 插入并利用时间间隔作为优化。这样做的缺点当然是在线程 B 准备运行时没有释放锁的风险,此时线程 B 会注意到锁不可用并再次进入等待集。然而,从统计上可以证明,非公平锁定在大多数情况下提供了更好的性能。

顺便说一句,您可以使用公平锁,等待线程按照它们获取锁的顺序恢复,但实际上这会提供更差的性能。在此处阅读有关此内容的更多信息:http: //docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/locks/ReentrantLock.html

我希望这回答了你的问题。

于 2012-12-07T07:59:27.280 回答
1

不,这不是真的。当一个线程调用 notify 时,会唤醒一个等待线程(如果存在这样的线程,则通知丢失)。可能您的同事想到了“虚假通知”,它可以在实际上没有其他线程调用时唤醒一个线程notifynotifyAll. 为了过滤“虚假通知”,每次notify调用都应伴随受监视对象状态的一些变化,等待线程应检查该状态:

 synchronized void up() {
    counter++;
    notify();
 }
 synchronized void down() {
   while (counter==0) {
      wait();
   }
   counter--;
 }

注意检查状态down()是在调用 wait() 之前完成的,因为它可以在调用之前更改并且通知丢失。换句话说,真正的信息与对象的状态一起传递,等待/通知仅有助于避免轮询。永远不要在不改变对象状态的情况下依赖通知。

于 2012-12-07T08:11:26.770 回答