Java 等待线程是否重新访问同步方法中的代码

Java 等待线程是否重新访问同步方法中的代码,java,multithreading,wait,Java,Multithreading,Wait,我在读关于线程同步和wait/notify构造的文章。它说 调用wait时,线程释放锁并暂停执行。在将来的某个时候,另一个线程将获取相同的锁并调用Object.notifyAll,通知等待该锁的所有线程发生了重要的事情 在第二个线程释放锁后的一段时间,第一个线程通过调用wait返回来重新获得锁并恢复 另外,如果有多个线程可以在第一个线程被notify唤醒时竞争锁,那么其中任何一个线程都可以拥有该对象上的锁。我的问题是,如果第一个线程本身重新获取锁,它是否必须从同步方法的开始重新开始(这意味着,它

我在读关于线程同步和wait/notify构造的文章。它说

调用wait时,线程释放锁并暂停执行。在将来的某个时候,另一个线程将获取相同的锁并调用Object.notifyAll,通知等待该锁的所有线程发生了重要的事情

在第二个线程释放锁后的一段时间,第一个线程通过调用wait返回来重新获得锁并恢复

另外,如果有多个线程可以在第一个线程被
notify
唤醒时竞争锁,那么其中任何一个线程都可以拥有该对象上的锁。我的问题是,如果第一个线程本身重新获取锁,它是否必须从同步方法的开始重新开始(这意味着,它在while循环检查wait()条件之前再次执行代码),还是只是在
wait()
行暂停

// Does the waiting thread come back here while trying to own the
// lock (competing with others)?
public synchronized notifyJoy() {
    // Some code  => Does this piece of code gets executed again then in case
    // waiting thread restarts its execution from the method after it is notified?
    while (!joy) {
        try {
            // Does the waiting thread stay here while trying to re-acquire
            // the lock?
            wait();
        } catch(InterruptedException e) {}
    }
    // Some other code
}
        

线程执行直接在调用wait之后开始。它不会从头开始重新启动块。wait()的实现大致类似于

public void wait() {
    release_monitor();
    wait_monitor();
    acquire_monitor();
}
这与它的实际实现方式无关,它只是对幕后发生的事情的粗略了解。每个对象都有一个与之关联的监视器,可以获取和释放该监视器。一次只有一个线程可以保存监视器,一个线程可以递归地获取监视器,而不会出现任何问题。调用等待对象将释放监视器,允许另一个线程获取它。等待线程然后等待,直到它被notify/notifyAll调用唤醒。当被唤醒时,等待的线程再次等待以要求对象的监视器并返回调用代码

例如:

private Object LOCK = new Object;
private int num = 0;
public int get() {
    synchronized( LOCK ) {
        System.out.println( "Entering get block." );

        LOCK.wait();

        return num;
    }
}

public void set( int num ) {
    synchronized( LOCK ) {
        System.out.println( "Entering set block." );

        this.num = num;

        LOCK.notify();
     }
}

“进入get块。”
对于每次调用
get()

一个方法只会在执行它的线程完成其run方法的执行时被打印一次,无论是通过正常返回还是在该run方法中抛出一个未捕获的异常。在上述情况发生之前,您的方法不执行的唯一方法是将JVM从您下面杀死(使用java.lang.System.exit,使用kill-9杀死java进程,等等),或者如果该方法在JVM关闭的守护进程线程中运行。这里没有什么奇怪的事。等待的线程放弃锁并进入休眠状态,但它不会以某种方式停止执行该方法

线程从等待的召唤中苏醒过来,再也没有去任何地方;在线程等待的整个过程中,它仍然处于wait方法中。在离开wait方法之前,它首先必须获取它为了开始等待而放弃的锁。然后,它需要重新测试它需要检查的任何条件,然后才能知道是否继续等待

这就是为什么告诉您等待必须在循环中完成:

等待的调用不会返回,直到另一个线程发出可能已发生某些特殊事件的通知—尽管不一定是该线程正在等待的事件:

注意:始终在测试等待条件的循环中调用wait。不要假设中断是针对您正在等待的特定条件,或者该条件仍然为真

(本教程使用的措辞具有误导性;单词“interrupt”应为“notification”。此外,不幸的是,所示的教程代码在没有设置中断标志的情况下吃掉了InterruptedException,最好让InterruptedException从此方法中抛出,而不捕获它。)


如果线程确实“重新开始”,那么就不需要这个循环;您的代码将从方法的开头开始,获取锁,并测试正在等待的条件。

否。等待线程将从
wait
命令恢复。@laune我知道wait/notify主要用于发送信号,而synchronized则用于锁访问。我唯一的困惑是,线程在将其状态从
waiting
更改为
blocked
再更改为
runnable
后,是从离开的位置恢复,还是必须返回并重新启动@Boris似乎在评论中回答了这个问题。你的例子很容易“丢失通知”。如果线程A在线程B调用set()之前调用get(),那么它可能会按照您想象的方式工作,但是如果线程B首先调用set(),并且只调用一次,那么线程A将永远等待()。如果其他线程尚未在foo.wait()调用中等待,则foo.notify()调用不会执行任何操作。[†阅读“虚假唤醒”]。@jameslarge,Ya这是一个很糟糕的示例,但编写它是为了表明该块只执行一次,而不是为了好的样式。请确保在循环中等待。同一线程的递归获取称为重入。wait方法持有一个需要监视器的线程队列,它的作用类似于一个门,而不像您获取的东西。文档还说,一次只能有一个线程在该块中。一个线程如何可能在该块中执行了部分块并等待,而另一个拥有锁的线程也可以执行该块的部分或全部?从技术上讲,有两个线程——尽管其中一个正在等待——因此违反了“sychronized.method/block中只允许一个线程”的原则@veritas:一个线程在等待方法中,而另一个方法在执行该方法中的其他代码,这是对的。只有一条线有锁。因此,可能会有多个等待线程,因为它们没有锁。因此,您所说的文档实际上意味着只有一个线程可以“拥有”该方法的锁,而不一定只有一个线程可以“在”该方法中,因为从技术上讲,正如您所说,多个线程可以等待
public synchronized void guardedJoy() {
    // This guard only loops once for each special event, which may not
    // be the event we're waiting for.
    while(!joy) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}