关于join()的一个小坑

2020年7月15日
关于join()的一个小坑插图

本文出自明月工作室:https://www.freebytes.net/it/java/thread-join.html

很多情况下,主线程创建并启动子线程,如果子线程中要进行比较耗时的运算,并且希望主线程等待子线程执行完毕之后再执行。那么就可以使用join()方法,它是线程类Thread的一个用于等待线程对象销毁的方法,底层是wait()方法。

join()的使用比较简单,但是有个小坑需要注意一下。

下面看这个例子:

public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        ThreadJoin threadJoin = new ThreadJoin();
        threadJoin.start();
        threadJoin.join(1000);
        System.out.println("main结束----------");
    }
}

class ThreadJoin extends Thread {
    @Override
    public  void run() {
        System.out.println("线程开始-------");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程结束-------");
    }
}

程序的输出结果是——

线程开始-------
main结束----------
线程结束-------

结果是正常的,因为join(1000)表示主线程最多只等待子线程1秒的时间。

但是如果给线程类的run方法添加一个同步锁——

    @Override
    public synchronized void run() {
        System.out.println("线程开始-------");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程结束-------");
    }

那么输出的结果就会变成——

线程开始-------
线程结束-------
main结束----------

可见,主线程虽然通过join(1000)声明只等待子线程1秒的时间,但实际上却是等子线程执行完毕了,一共2秒的时间。这是为什么呢?

看一下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,可见join是同步方法,锁住的对象监视器是线程对象本身。而ThreadJoin线程的run方法也是同步的,锁住的也是线程对象本身。

因此,在这个例子中,threadJoin.start()语句就和threadJoin.join(1000)语句互相争抢锁, threadJoin.start() 先抢到了, threadJoin.join(1000) 就不能执行,只能等到 threadJoin 的run方法执行完毕之后再执行。