Thread.join() 和 Object.wait( )方法思考
先看这个例子:在main线程里面,创建了一个新的线程sonThread,要求 son线程执行完之后,再执行join后面的代码
public static void main(String[] args) {
Thread sonThread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(" A END");
}
},"newThread");
sonThread.start();
try {
sonThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main end");
}
这行代码最终会输出
A END
main end
我们要研究的就是 join方法为何会阻塞住 main线程,直到 son线程执行完成。
join源码
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
...
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
...
}
join方法的追踪很简单,最终是指向 Thread#join(0)方法,因为 millis ==0 成立,所以关键的代码就在 while (isAlive())判断,和里面的wait(0)方法。
Thread#isAlive() 方法
这是一个native方法,用来判断当前线程是否存活,这里的 isAlive 判断的是调用 isAlive()方法的Thread对象,而并不是当前线程。
结合上面的例子:上面是由 sonThread.join( ) 方法进入,所以,isAlive判断的是sonThread 这个对象,并不是main线程,我曾经错误认为,isAlive判断的是main线程。
这里需要注意: sonThread对象在创建后,是存在于jvm中,在调用 sonThread.start( ) 方法后,由JVM(用户态)向系统os申请创建一个线程(内核态),然后 jvm中的这个线程对象会和 os 创建好的线程进行绑定。 os创建好的线程会去调用 JVM对象中的run方法。
也就是:
1.jvm创建线程对象,此时为用户态;
2.线程对象.start() 方法,jvm(C++代码)向操作系统申请,创建一个内核级别的线程,此时有用户态向内核态切换,即人们常说的创建线程的成本。
3.完成绑定。
4.os的线程去 call JVM中线程对象的 run 方法。
完成了这样的区分后,就清楚了, sonThread.alive( )方法返回true的时候,表示sonThread所绑定的线程处于任务中,则进入 wait(0);
Objecte#wait(0)
wait(0)方法是 Thread类继承自 Object的方法,此时,我强烈建议把关注点从 Thread类移开,清楚区分,这是Object类提供的 wait方法,它表示,让持有 object对象的线程释放 object的锁,并阻塞住这个线程。
while (isAlive()) {
wait(0);
}
在这行代码里,wait(0)的方法是被 sonThread这个对象调用,所以this指的是sonThread这个对象,持有这个sonThread对象的是 main线程。
main线程释放 sonThread这个对象的锁(注意,join方法是 syn修饰的,这表示,进入wait(0)代码时,当前线程必定是已经获取了 sonThread对象的锁,先获取,再释放,这是jvm规范)。释放后,就处于阻塞状态,等待唤醒。
main何时被唤醒
这里借用一下这篇文章https://blog.csdn.net/u010983881/article/details/80257703,在jvm源码里,线程执行完后有一个ensure_join,会唤醒阻塞在这个线程对象上的其他线程。
static void ensure_join(JavaThread* thread) {
// We do not need to grap the Threads_lock, since we are operating on ourself.
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "java thread object must exist");
ObjectLocker lock(threadObj, thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
// Thread is exiting. So set thread_status field in java.lang.Thread class to TERMINATED.
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
// Clear the native thread instance - this makes isAlive return false and allows the join()
// to complete once we've done the notify_all below
java_lang_Thread::set_thread(threadObj(), NULL);
// 同志们看到了没,别的不用看,就看这一句
// thread就是当前线程,是啥?就是刚才例子中说的threadA线程啊。
lock.notify_all(thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
wait(0)时,main线程释放了 sonThread这个线程对象的锁,并阻塞在sonThread这个线程对象上,当sonThread锁绑定的线程执行完后,又会唤醒 main线程,所以,wait(0)这行代码执行完成,此时,重新回到 while()循环,即
while (isAlive()) {
wait(0);
}
这块循环中,此时 sonThread所绑定的os线程已经完成了任务,不再活跃,sonThread.isAlive也就返回false,跳出while()循环,join方法执行完成,main线程得到继续执行,最终完成。
这就是 join能够产生阻塞的原理。
总结如下:
1.main线程创建新的线程对象 sonThread;
2.sonThread.start() 完成os线程创建和绑定,os线程开始异步执行。
3.sonThread.join(),进入到 while(isAlive())循环判断,只要 sonThread对象所绑定的os线程处于存活状态,sonThread.isAlive就会返回true,进入wait(0)。
4.sonThread.wait(0),会让持有sonThred对象的线程阻塞,join方法是syn方法,调用这个join方法的线程释放锁,并一直阻塞,直到被唤醒。
5.sonThread所绑定的os线程在执行完后,会唤醒阻塞在sonThread对象上的线程,这些线程会再次活跃起来,尝试获取sonThread对象的锁。
6.阻塞结束,完成目的。