很长一段时间都对join()方法有疑惑,主要有以下2点(为了表述清楚,把现在执行的线程称为main线程,在main中调用join()的线程称为sub线程)
(1)join()到底怎么实现main线程阻塞,sub线程执行的
(2)sub线程执行玩之后为什么main线程会被唤醒
为了解决这个问题,可以从这样一个例子出发
public static void main(String[] args){
Thread t1=new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(300);
}catch (Exception e){}
System.out.println(Thread.currentThread().getId()+"-"+"sub线程");
}
});
try{
t1.start();
t1.join();
}catch (Exception e){}
System.out.println("main线程执行");
}
执行结果如下:
11-sub线程
11-sub线程
11-sub线程
11-sub线程
11-sub线程
main线程执行
出现这样的结果在预料之中,因为join()的作用就是让正在执行的main线程阻塞,先执行sub线程,之后再继续执行main线程。不过具体实现的细节值得讨论。
执行过程如下:
(1)在调用join之后进入join()方法体执行(这里调用的是join(),也就是不设置时间)
join()的源码如下:
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()方法实际上是一个synchronized修饰的同步方法,这个意味着执行这个方法的线程会获得该方法所属对象实例的锁;而执行这个方法的正是main线程,这个对象的实例实际上就是ti(也就是sub线程对应的对象实例),所以main线程获得了ti的对象锁之后开始执行。
(2)因为millis为0,所以以下部分被执行
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
isAlive()方法实际上就是this.isAlive(),也就是t1.isAlive(),因为ti.start()已经执行,t1.isAlive()为true,所以进入执行wait(0)。wait(0)方法的作用是阻塞当前执行的线程,也就是执行他的main线程(需要注意:虽然是t1.wait(0),但是阻塞的是main线程,并且main线程阻塞之后释放t1的对象锁然后进入t1对象的等待池中),sub线程开始执行。
(3)在sub线程执行完之后,退出时的实现如下
static void ensure_join(JavaThread* thread) {
Handle threadObj(thread, thread->threadObj());
assert(threadObj.not_null(), "Java thread object must exist");
ObjectLocker lock(threadObj, thread);
thread->clear_pending_exception();
java_lang_Thread::set_stillborn(threadObj());
java_lang_Thread::set_thread_status(threadObj(), java_lang_Thread::TERMINATED);
java_lang_Thread::set_thread(threadObj(), NULL);
lock.notify_all(thread);
thread->clear_pending_exception();
}
可以看出在线程执行完毕之后退出时,会执行notify_all(thread),也就是notify等待池中所有的线程,所以在sub执行完毕之后,main线程被唤醒,也就是从t1的等待池中出来进入t1的锁池(这里锁池中为空,所以main线程会直接执行)。main线程执行完毕之后程序执行完毕。
注意:如果millis不是0,那么那么执行wait(millis),表示在等待millis时间之后如果main线程会自动被唤醒