java - 何时调用Object.wait

标签 java multithreading

当我们有一个线程正在执行同步块(synchronized block)时,还有另一个线程试图访问该同步块(synchronized block)。

  • 是否会在阻塞的线程上自动调用Object.wait?
  • 另外,我看到在Object类中,wait的定义是:
    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();
        }
      }
    

    最佳答案

    不,不会调用Object::waitwait/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。事件的顺序是:
  • A尝试获取锁
  • A成功获取锁
  • A将“first before”写入System.out
  • B尝试获取锁
  • B无法获得该锁,因为A拥有它,所以它阻止了
  • A完成编写,并调用wait-此时释放锁定,而是开始等待
  • B现在成功获取了锁定,并解除了对
  • 的阻止
  • B将“第二秒”写入System.out
  • B完成编写,并调用notify-这对B无效,但这意味着A 停止等待并尝试重新获取锁
  • A无法获得锁定,因为B拥有它,所以它阻止了
  • B将“second after”写入System.out
  • B完成该方法,然后释放锁
  • A现在成功获取了锁定,并解除了对
  • 的阻止
  • A继续进行操作,然后将“first after”写入System.out
  • A完成该方法,并释放锁

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

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

    我已经将wait/notify描述为一个线程借给另一个锁。这就是我的想法,我认为这是一个富有成效的隐喻。用一个局部怪异的比喻,也许就像一个吸血鬼走进城堡,然后睡在棺材里 sleep 。一旦他睡着了,一些无辜的游客就会进来并将城堡出租作为度假屋。在某个时候,游客探索地下室并打扰棺材,这时吸血鬼醒来并要他的城堡归还。一旦游客惊恐逃离,他就可以搬回房子。
    waitnotify具有其名称的原因,而不是像lendreturn之类的名称,是因为它们通常用于建立线程间通信机制,其中的重点不在第一个线程对锁的初始借出,但是在第二线程唤醒服务员时。

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

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

    The thread may be removed from the wait set due to [...] An internal action by the implementation. Implementations are permitted, although not encouraged, to perform "spurious wake-ups", that is, to remove threads from wait sets and thus enable resumption without explicit instructions to do so.



    也就是说,线程通常只会在收到通知时唤醒,但是有可能在没有通知的情况下随机唤醒。因此,您确实需要使用涉及检查条件变量的循环来保护wait,这与您的示例完全相同。如规范所述:

    Notice that this provision necessitates the Java coding practice of using wait only within loops that terminate only when some logical condition that the thread is waiting for holds.



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

    关于java - 何时调用Object.wait,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19766069/

    相关文章:

    java - Spring REST获取多个映射端点的url路径

    java - 在 Spring Boot/Angular 中存储/更新图像以及实体数据

    java - 将 RDF .ttl 文件合并到一个文件数据库中 - 过滤并仅保留所需的数据/三元组

    python - 用于图像特征提取的 tensorflow 多重处理

    multithreading - Perl线程: How to make a producer?

    java - 在无参数公共(public)方法上@Inject

    java - 将上下文从 Service 传递到 Asynctask 而不会泄漏

    java - Java 中的 "implements Runnable"与 "extends Thread"

    c++ - 为什么lock_guard需要Lockable概念

    python - 函数内部的并行性?