什么时候调用Object.wait

时间:2013-11-04 10:47:09

标签: java multithreading

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

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

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

  3. 这是否意味着我们必须手动编写类似下面的函数。我见过很多例子:

    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();
        }
      }
    

2 个答案:

答案 0 :(得分:6)

不,Object::wait将不会被调用。 wait / notify机制是synchronized提供的基本锁定之上的附加层;在不使用synchronizedwait的情况下,可以使用notify

基本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. 成功获得锁定
  3. A先写"然后先#34;到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. 现在成功获取锁定,并取消阻止
  14. A继续写下"在"之后到System.out
  15. A完成方法,并释放锁
  16. 这是一个冗长的描述,但它真的是一个非常简单的过程 - wait / notify调用让第一个线程将锁提供给另一个线程来使用

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

    我已经将wait / notify描述为一个线程借给另一个锁。这就是我的想法,我认为这是一个富有成效的比喻。使用一个局部幽灵般的比喻,也许它就像一个移动到城堡里的吸血鬼,然后在他的棺材里睡觉。一旦他睡着了,一些无辜的游客进来并将这座城堡作为度假屋出租。在某些时候,游客探索地穴并打扰棺材,此时吸血鬼醒来并希望他的城堡回来。一旦游客恐怖逃离,他就可以搬回家里。

    waitnotify拥有其名称的原因,而不是像lendreturn这样的名称,是因为它们通常用于构建线程间通信机制,重点不在于第一个线程对锁的初始借出,而在于第二个线程对服务员的唤醒。

    现在,最后转到第二个问题,有两件事要考虑。

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

      

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

    也就是说,线程通常只会在收到通知时唤醒,但是当它们没有得到通知时,它们可能会随机唤醒。因此,您需要使用包含检查条件变量的循环来保护wait,与示例中的完全相同。正如规范所说:

      

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

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

答案 1 :(得分:2)

  

是否会在被阻止的线程上自动调用Object.wait?

不是真的,它阻止了它的显示器锁定。您可以参考文档for details on how this works。无论如何,关于如何在内部完成此操作的详细信息与使用synchronized无关,您可以使用它,因为知道在任何给定时间只允许一个线程执行同步块。

  

这是否意味着我们必须手动编写类似的函数   在我们班级的下面

不,同步为你做了。同步块一次只能由一个线程访问,因此第二个线程将阻塞,直到第一个线程退出同步块。