引言

这篇文章,我们来看Thread.join方法的使用。

join方法的实现逻辑

首先,我们来看jdk中join方法是如何实现的:

  1. public final synchronized void join(long millis)
  2. throws InterruptedException {
  3. long base = System.currentTimeMillis();
  4. long now = 0;
  5. if (millis < 0) {
  6. throw new IllegalArgumentException("timeout value is negative");
  7. }
  8. if (millis == 0) {
  9. while (isAlive()) {
  10. wait(0);
  11. }
  12. } else {
  13. while (isAlive()) {
  14. long delay = millis - now;
  15. if (delay <= 0) {
  16. break;
  17. }
  18. wait(delay);
  19. now = System.currentTimeMillis() - base;
  20. }
  21. }
  22. }

可以发现它是基于Object.wait()方法实现的,因为wait()方法的调用前提是持有对象锁,所以join()方法被synchronized修饰。调用wait方法后,线程会处于WAITING状态而不会继续运行。
这里有一个不太好理解的地方,假设当前有两个线程:主线程和线程A,主线程调用线程A对象的join方法,那么哪个线程会继续运行,哪个线程会处于WAITING状态?看下面的例子:

  1. public class JoinTest {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread threadA = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. while (true){
  7. System.out.println("threadA is running");
  8. }
  9. }
  10. },"threadA");
  11. threadA.start();
  12. threadA.join();
  13. System.out.println("the main thread is running");
  14. }
  15. }

在这个例子中,主线程(main线程)调用了threadA的join方法,运行时,会一直输出threadA is running,而不会输出the main thread is running。说明处于WAITING状态的是主线程。
为什么会这样?我们从join方法的实现来分析:
首先main线程调用threadA对象的join方法,获取到了threadA的对象锁,然后调用wait()方法,实际上调用的是threadA这个Thread实例对象的wait方法,所以main线程会释放持有的threadA对象锁,然后进入WAITING状态。其实整个逻辑比较简单,只是对象锁是在一个代表线程threadA的对象上面,所以有些迷惑。

join的唤醒

根据join方法的逻辑,我们看到,当参数是0时,执行的是wait(0)方法,执行之后线程会一直处于WAITING状态直到其他线程执行了notify或者notifyAll方法才会被唤醒,但是join方法里面并没有notify的逻辑,那么调用join方法的线程就一直WAITING下去,不能被唤醒了吗?不是的,当使其处于WAITING的thread对象代表的线程执行完成后,该线程就会被唤醒,看下面的例子:

  1. public class JoinTest {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread threadA = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. for(int i=0;i<100;i++){
  7. System.out.println(i);
  8. }
  9. }
  10. },"threadA");
  11. threadA.start();
  12. threadA.join(0);
  13. System.err.println("the main thread is running");
  14. }
  15. }

主线程调用threadA的join方法,在threadA对象锁上处于WAITING状态,在threadA线程执行完成后,就会被唤醒,输出如下:

  1. 0
  2. 1
  3. 2
  4. 3
  5. 4
  6. 5
  7. 6
  8. 7
  9. 8
  10. 9
  11. the main thread is running

如果join方法的参数不为零呢?它执行的是wait(对应的时间),也就是当对应的时间过去之后,处于WAITING状态的线程会自动唤醒从而继续执行,而不需要等到threadA执行完成,看下面的例子:

  1. public class JoinTest {
  2. public static void main(String[] args) throws InterruptedException {
  3. Thread threadA = new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. try {
  7. Thread.sleep(10000);
  8. System.out.println("the threadA finished sleep");
  9. } catch (InterruptedException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. },"threadA");
  14. threadA.start();
  15. threadA.join(3000);
  16. System.err.println("the main thread is running");
  17. }
  18. }

threadA睡眠10秒钟,join的参数是3秒钟,3秒钟之后main方法就会执行,输出如下:

  1. the main thread is running
  2. the threadA finished sleep

join的使用

因为join方法可以让一个线程在另外一个线程运行完成之后再运行,所以可以用来安排不同线程的执行顺序,看下面的例子:

  1. public class Join extends Thread {
  2. private Thread previousThread;
  3. private int order;
  4. public Join(Thread previousThread,int order) {
  5. this.previousThread = previousThread;
  6. this.order = order;
  7. }
  8. @Override
  9. public void run() {
  10. try {
  11. previousThread.join(0);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. System.out.println(this.order);
  16. }
  17. public static void main(String[] args) throws InterruptedException {
  18. Thread previousThread = Thread.currentThread();
  19. for(int i = 0; i<100; i++){
  20. Join join = new Join(previousThread,i);
  21. join.start();
  22. previousThread = join;
  23. }
  24. }
  25. }

这个例子中,每个线程都在前一个线程运行完成之后再运行,结果会按顺序输出0到99。

小结

Thread.join()方法的实现比较有迷惑性并且在实际中使用的并不多,我们明白了它的实现原理之后能够用它的特性实现一些对线程执行前后顺序有要求的功能。