join()方法实例演示及源码分析
以下为测试代码,下文围绕该代码展开:
public class MyThread1 implements Runnable {
public void run() {
System.out.printf("Beginning MyThread1: %s\n", new Date());
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("MyThread1 has finished: %s\n", new Date());
}
}
public class MyThread2 implements Runnable {
public void run() {
System.out.printf("Beginning MyThread2: %s\n", new Date());
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("MyThread2 has finished: %s\n", new Date());
}
public static void main(String[] args) {
MyThread1 myThread1 = new MyThread1 ();
Thread thread1 = new Thread(myThread1, "MyThread1");
MyThread2 myThread2 = new MyThread2 ();
Thread thread2 = new Thread(myThread2, "MyThread2 ");
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join(1800);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("Main: Configuration has been loaded: %s\n", new Date());
}
}
分析:thread1.start(); thread2.start(); 两个线程开启,抢占CPU资源顺序无法确定,所以第一句执行的语句无法确定。
继续 thread1.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;
}
}
}
主要分析:
public final synchronized void join(long millis)
throws InterruptedException {
while (isAlive()) {
wait(0);
}
主线程获取join()方法的锁,判断thread1线程是否存活,若存活则则调用wait()方法,wait()方法只会让持有锁的线程进入等待,因此主线程阻塞,进入等待(join方法会造成当前线程wait,就如你看到的这里的wait(0),是当前线程wait,并不是调用者wait)。所以2s之后thread1的结束线程语句打印,主线程被唤醒,继续执行thread2.join(1800); 主线程继续阻塞,当过了1.8s之后,thread2依然没有执行完毕,从源码可知,此时会break,主线程被唤醒,继续执行完主线程结束语句,最后才会执行thread2执行结束语句。
打印结果:
Beginning MyThread1: Thu Jul 11 17:04:37 CST 2019
Beginning MyThread2: Thu Jul 11 17:04:37 CST 2019
MyThread1 has finished: Thu Jul 11 17:04:39 CST 2019
Main: Configuration has been loaded: Thu Jul 11 17:04:40 CST 2019
MyThread2 has finished: Thu Jul 11 17:04:41 CST 2019
或者:
Beginning MyThread2: Thu Jul 11 17:05:18 CST 2019
Beginning MyThread1: Thu Jul 11 17:05:18 CST 2019
MyThread1 has finished: Thu Jul 11 17:05:20 CST 2019
Main: Configuration has been loaded: Thu Jul 11 17:05:22 CST 2019
MyThread2 has finished: Thu Jul 11 17:05:22 CST 2019
至于thread1执行完毕,主线程被唤醒,但是从上面上源代码里并没有看到notify调用,而且文档上表明不建议应用程序自己去调用,最终答案在jvm C++源码里,源码之下:
void JavaThread::run() {
...
thread_main_inner();
}
void JavaThread::thread_main_inner() {
...
this->exit(false);
delete this;
}
void JavaThread::exit(bool destroy_vm, ExitType exit_type) {
...
// Notify waiters on thread object. This has to be done after exit() is called
// on the thread (if the thread is the last thread in a daemon ThreadGroup the
// group should have the destroyed bit set before waiters are notified).
ensure_join(this);
...
}
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);
lock.notify_all(thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
重点看ensure_join 方法里的 lock.notify_all(thread);语句代码说明jvm会在线程B执行完成后帮我们调用notify_all()方法,这样主线程就会重回Runnable状态,时间片分配后主线程可执行join方法之后的代码。