源码
/**
* Waits for this thread to die.
*
* <p> An invocation of this method behaves in exactly the same
* way as the invocation
*
* <blockquote>
* {@linkplain #join(long) join}{@code (0)}
* </blockquote>
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final void join() throws InterruptedException {
join(0);
}
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
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()方法的作用,是等待这个线程结束。从源码中不难发现,其实Join()方法最终调用的是wait()方法。
public class ThreadJoin {
public static void main(String[] args) {
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println(""+Thread.currentThread().getName());
}
}
},"Thread1");
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("任务执行完成");
}
}
"main" #1 prio=5 os_prio=0 tid=0x0337dc00 nid=0x2f38 in Object.wait() [0x0162f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0ab717e0> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1252)
- locked <0x0ab717e0> (a java.lang.Thread)
at java.lang.Thread.join(Thread.java:1326)
at com.idea.thread.join.ThreadJoin.main(ThreadJoin.java:17)
"Thread1" #9 prio=5 os_prio=0 tid=0x15b5a800 nid=0x1f30 runnable [0x1618f000]
java.lang.Thread.State: RUNNABLE
at java.io.FileOutputStream.writeBytes(Native Method)
at java.io.FileOutputStream.write(FileOutputStream.java:326)
at java.io.BufferedOutputStream.flushBuffer(BufferedOutputStream.java:82)
at java.io.BufferedOutputStream.flush(BufferedOutputStream.java:140)
- locked <0x0ab92a58> (a java.io.BufferedOutputStream)
at java.io.PrintStream.write(PrintStream.java:482)
- locked <0x0ab71958> (a java.io.PrintStream)
at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221)
at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291)
at sun.nio.cs.StreamEncoder.flushBuffer(StreamEncoder.java:104)
- locked <0x0ab92a98> (a java.io.OutputStreamWriter)
at java.io.OutputStreamWriter.flushBuffer(OutputStreamWriter.java:185)
at java.io.PrintStream.newLine(PrintStream.java:546)
- locked <0x0ab71958> (a java.io.PrintStream)
at java.io.PrintStream.println(PrintStream.java:807)
- locked <0x0ab71958> (a java.io.PrintStream)
at com.idea.thread.join.ThreadJoin$1.run(ThreadJoin.java:11)
at java.lang.Thread.run(Thread.java:748)
有很多人不理解join为什么阻塞的是主线程呢? 不理解的原因是阻塞主线程的方法是放在thread这个实例作用,让大家误以为应该阻塞thread线程。实际上主线程会持有thread这个对象的锁,然后调用wait方法去阻塞,而这个方法的调用者是在主线程中的,所以造成主线程阻塞。
public class ThreadJoin {
public static void main(String[] args) {
Thread current = Thread.currentThread();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("" + Thread.currentThread().getName());
}
}
}, "Thread1");
thread.start();
synchronized (current) {
try {
current.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("任务执行完成");
}
}
Join方法实质就是调用当前线程的wait()方法
有了wait(),必然有notify(),什么时候才会notify呢?在jvm源码里:
// 位于/hotspot/src/share/vm/runtime/thread.cpp中
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);
// 别的不用看,就看这一句
// thread就是当前线程,是啥?就是刚才例子中说的threadA线程啊。
lock.notify_all(thread);
// Ignore pending exception (ThreadDeath), since we are exiting anyway
thread->clear_pending_exception();
}
当子线程thread1执行完毕的时候,jvm会自动唤醒阻塞在thread1对象上的线程,在我们的例子中也就是主线程。至此,thread1线程对象被notifyall了,那么主线程也就能继续跑下去了。
总结
join() 是一个synchronized方法, 里面调用了wait(),这个过程的目的是让持有这个同步锁的线程进入等待,那么谁持有了这个同步锁呢?答案是主线程,因为主线程调用了thread1.join()方法,相当于在thread1.join()代码这块写了一个同步代码块,谁去执行了这段代码呢,是主线程,所以主线程被wait()了。然后在子线程thread1执行完毕之后,JVM会调用lock.notify_all(thread);唤醒持有thread1这个对象锁的线程,也就是主线程,会继续执行。