[笔记] 线程对象wait不需要notify也能唤醒?

描述

今天偶然地运行了下面一段代码:

public class WaitMain {

    public static void main(String[] args) {

        Thread t1 = new MyThread();

        synchronized (t1) {
            try {
                t1.start();

                t1.wait();

                System.out.println("Main end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class MyThread extends Thread {

    public void run() {
        System.out.println("Thread run");
    }
}

在main方法里面创建了一个线程t1,并启动了线程t1,之后调用了t1.wait让当前线程进入等待,这时如果不调用t1.notify()/notifyAll()的话,当前线程应该会一直阻塞住的,但是运行的结果却是:

Thread run  
Main end

这个结果让我很惊讶,main方法的线程又被唤醒了,印象中的应该是wait后必须有notify/notifyAll后线程才能从等待状态进入就绪状态的。

验证

接着我注释掉了t1.start()方法,不启动t1线程:

public class WaitMain {

    public static void main(String[] args) {

        Thread t1 = new MyThread();

        synchronized (t1) {
            try {
//                t1.start();
                t1.wait();

                System.out.println("Main end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class MyThread extends Thread {

    public void run() {
        System.out.println("Thread run");
    }
}

这下线程阻塞住了,不会打印"Main end"!那么问题就出在了t1.start()上,接着在新线程中加个Thread.sleep():

public class WaitMain {

    public static void main(String[] args) {

        Thread t1 = new MyThread();

        synchronized (t1) {
            try {
                t1.start();

                t1.wait();

                System.out.println("Main end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class MyThread extends Thread {

    public void run() {
        System.out.println("Thread run");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

Thread run  
---等待2秒----
Main end  

这个结果表明,当t1线程运行完之后,会唤醒等待的线程。

join()

在观察Thread的join源码的时候,发现其内部就是通过wait来实现当前线程等待的,我们先看看它的实现:

public final synchronized void join(long millis)  
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

可以看到join方法有synchronize修饰的,并且里面是调用了wait方法,那么这里的操作其实就等同于我们一开始所给出的例子了。根据isAlive()判断线程是否存活,如果isAlive()返回true,则调用wait阻塞当前线程,可以看到这里也没有调用notify方法,那么阻塞的线程是怎么被唤醒的呢?

终于在知乎找到了一个解答:Java中Thread类的join方法到底是如何实现等待的?

这里给出其解答:

大家都知道,有了wait,必然有notify,我刚保证楼主在整个jdk里面都不会找到对b线程对象的notify操作。这就要看jvm代码了:

作者:cao 链接:https://www.zhihu.com/question/44621343/answer/97640972 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

//一个c++函数:
void JavaThread::exit(bool destroy_vm, ExitType exit_type) ;

//这家伙是啥,就是一个线程执行完毕之后,jvm会做的事,做清理啊收尾工作,
//里面有一个贼不起眼的一行代码,眼神不好还看不到的呢,就是这个:

ensure_join(this);

//翻译成中文叫 确保_join(这个);代码如下:

static void ensure_join(JavaThread* thread) {  
  Handle threadObj(thread, thread->threadObj());

  ObjectLocker lock(threadObj, thread);

  thread->clear_pending_exception();

  java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);

  java_lang_Thread::set_thread(threadObj(), NULL);

  //同志们看到了没,别的不用看,就看这一句,妈了个淡淡,
//thread就是当前线程,是啥是啥?就是刚才说的b线程啊。
  lock.notify_all(thread);

  thread->clear_pending_exception();
}

可以看到线程在运行结束的时候会调用其notify_all方法,那么这个谜题就解开了。

结论

1.线程对象的wait()方法运行后,可以不用其notify()方法退出,会在线程结束后,自动退出;
2.线程间的等待唤醒机制,最好不要用线程对象做同步锁!