【问题标题】:When does Object.wait gets calledObject.wait 什么时候被调用
【发布时间】:2013-11-05 18:29:29
【问题描述】:

当我们有一个线程正在执行同步块并且有另一个线程试图访问该同步块时。

  1. 会在阻塞线程上自动调用 Object.wait 吗?
  2. 另外,我看到在 Object 类中,等待的定义是:

    public final native void wait(long timeout) throws InterruptedException;

这是否意味着我们必须在我们的类中手动编写如下函数。我见过很多例子:

public void doWait(){
    synchronized(obj){
      while(!wasSignalled){
        try{
          obj.wait();
         } catch(InterruptedException e){...}
      }
      //clear signal and continue running.
      wasSignalled = false;
    }
  }

public void doNotify(){
    synchronized(obj){
      wasSignalled = true;
      obj.notify();
    }
  }

【问题讨论】:

  • 我不知道你想问什么。
  • 我对流程感到困惑!当线程 A 试图访问另一个线程 B 已经访问过的同步块时,线程 A 会进入阻塞状态,对吗?那么,什么时候去等待呢?我在问被阻塞的线程A是否自动进入等待状态?

标签: java multithreading


【解决方案1】:

不,Object::wait 不会被调用。 wait/notify 机制是在synchronized 提供的基本锁定之上的附加层;可以使用synchronized 而无需使用waitnotify

基本的synchronized 机制基于锁定和解锁附加到特定对象的锁(该锁有时称为监视器)的思想。如果一个线程锁定了锁,那么另一个试图锁定它的线程将阻塞。当第一个线程解锁锁时,第二个线程解除阻塞,并继续锁定锁。

wait/notify 机制为线程提供了一种暂时放弃它持有的锁的方法,同时重新获取锁由其他持有该锁的线程控制。考虑这段代码:

public synchronized void first() {
    System.out.println("first before");
    wait();
    System.out.println("first after");
}

public synchronized void second() {
    System.out.println("second before");
    notify();
    System.out.println("second after");
}

假设一个线程 A 调用 first,然后另一个线程 B 调用 second。事件顺序是:

  1. A 尝试获取锁
  2. A成功获取锁
  3. A 将“first before”写入 System.out
  4. B 尝试获取锁
  5. B无法获取锁,因为A有,所以阻塞
  6. A 完成写入,并调用 wait - 此时 A 释放锁并开始等待
  7. B 现在成功获取锁,并解除阻塞
  8. B 将“秒前”写入 System.out
  9. B 完成写入,并调用 notify - 这对 B 没有影响,但这意味着 A 停止等待并尝试重新获取锁
  10. A无法获取锁,因为B有,所以阻塞
  11. B 将“秒后”写入 System.out
  12. B 完成方法,并释放锁
  13. A 现在成功获取锁,并解除阻塞
  14. A 继续往 System.out 写入“first after”
  15. A 完成方法,并释放锁

这是一个冗长的描述,但它确实是一个非常简单的过程 - wait/notify 调用有点让第一个线程将锁借给另一个线程使用。

重要的是要意识到有两种不同类型的阻塞正在发生。首先,线程在进入synchronized 块(或从wait 调用返回时重新输入一个)时阻塞等待获取锁的方式。其次,线程在调用wait 之后阻塞的方式,在被相应的notify 解除阻塞之前。

我将wait/notify 描述为一个线程借给另一个线程锁。这就是我的想法,我认为这是一个富有成效的比喻。用一个令人毛骨悚然的比喻来说,也许就像一个吸血鬼搬进城堡,然后睡在棺材里。一旦他睡着了,一些无辜的游客就会进来把城堡出租作为度假屋。在某个时候,游客探索了地下室并打扰了棺材,此时吸血鬼醒来并想要回到他的城堡。一旦游客惊恐逃走,他就可以搬回屋里。

之所以waitnotify有它们的名字,而不是像lendreturn这样的名字,是因为它们通常用于构建线程间通信机制,重点不在第一个线程最初借出锁,但在第二个线程唤醒等待者时。

现在,最后转向您的第二个问题,有两件事需要考虑。

第一个是“虚假唤醒”的可能性 - 请参阅 Java 语言规范的 17.2.1. Wait 部分中嵌套项目符号列表深处的小注释:

由于 [...] 实现的内部操作,线程可能会从等待集中移除。尽管不鼓励实现,但允许执行“虚假唤醒”,即从等待集中删除线程,从而在没有明确指令的情况下启用恢复。

也就是说,线程通常只会在收到通知时才唤醒,但也有可能在没有收到通知时随机唤醒。因此,您确实需要使用涉及检查条件变量的循环来保护wait,就像在您的示例中一样。正如规范所说:

请注意,此规定需要 Java 编码实践,即仅在循环中使用等待,该循环仅在线程等待的某些逻辑条件成立时终止。

第二个是中断。中断不是随机的;仅当其他线程在等待的线程上调用interrupt 时才会发生中断。发生这种情况时,它会立即停止阻塞,并从wait 调用中抛出一个InterruptedException。与您所看到的相反,捕获此异常并再次等待是正确的。原因很简单:如果有人在你的帖子上打了interrupt,那正是因为他们要你stop waiting!不可能确切地说出线程应该做什么,但通常的方法是中止当前工作,并将控制权返回给调用者。如果调用者在当前工作中止后不能继续,那么它也应该中止,依此类推,直到达到可以做一些有意义的事情的水平。正确处理中断是一个太大的主题,无法在这里处理,但首先要阅读教程中关于 Supporting Interruption 的内容,如果可能,请阅读 Java Concurrency In Practice

【讨论】:

  • +1 谢谢!这就解释了!在等待的部分有点困惑。 wait() 只是其他线程执行的另一种方式!
【解决方案2】:

Object.wait 会在被阻塞的线程上自动调用吗?

并非如此,它会阻塞其监视器锁。您可以参考文档for details on how this works。无论如何,如何在内部完成此操作的详细信息与使用 synchronized 并不真正相关,您只需知道在任何给定时间只允许一个线程执行同步块即可使用它。

这是否意味着我们必须手动编写一个像 在我们班下面

不,synchronized 会为您执行此操作。同步块一次只能被一个线程访问,因此第二个线程将阻塞,直到第一个线程退出同步块。

【讨论】:

  • 谢谢,但是当你说 - 同步为你做这件事时,你指的是哪个同步?我在 Object 类中没有看到任何同步等待方法
  • 这不是一个正确的答案。 Object.wait() 永远不会被隐式调用。当一个线程要访问同步块,而另一个线程有monitor时,它会进入一个monitor队列,这与调用wait()时不同,这意味着线程将等待notify()notifyAll() 被调用。
  • @MarkRotteveel 你是对的,它不会调用Object.wait。实际上,我从不关心 JVM 是如何做到这一点的。谢谢指正。
  • @UserInteractive 那么抱歉我不明白第二个问题。你的意思是如何使用wait 而不是synchronized 块?
  • @m0skit0 现在我看到 Object.wait 没有被隐式调用!那么,这是否意味着如果我们希望线程在收到通知之前处于非活动状态,我们必须在代码中调用它?
猜你喜欢
  • 2011-07-26
  • 2010-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多