0.Join()
线程的合并的含义就是 将几个并行线程的线程合并为一个单线程执行, 应用场景是 当一个线程必须等待另一个线程执行完毕才能执行时, Thread 类提供了 join 方法来完成这个功能, 注意, 它不是静态方法.
join 有 3 个重载的方法:
- void join(): 当前线程等该加入该线程后面, 等待该线程终止.
- void join(long millis): 当前线程等待该线程终止的时间最长为 millis 毫秒. 如果在 millis 时间内, 该线程没有执行完, 那么当前线程进入就绪状态, 重新等待 cpu 调度.
- void join(long millis,int nanos): 等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒. 如果在 millis 时间内, 该线程没有执行完, 那么当前线程进入就绪状态, 重新等待 cpu 调度.
参考: 猿码道: 啃碎并发(二)
1. 使用方法
新建一个 Thread 类, 重写 run()方法:
- public class MyThread extends Thread {
- @Override
- public void run() {
- System.out.println("子线程执行完毕");
- }
- }
新建测试类, 测试 Join()方法:
- public class TestThread {
- public static void main(String[] args) {
- // 循环五次
- for (int i = 0; i <5; i++) {
- MyThread thread = new MyThread();
- // 启动线程
- thread.start();
- try {
- // 调用 join()方法
- thread.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("主线程执行完毕");
- System.out.println("~~~~~~~~~~~~~~~");
- }
- }
- }
输出结果如下:
- 子线程执行完毕
- 主线程执行完毕
- ~~~~~~~~~~~~~~~
- 子线程执行完毕
- 主线程执行完毕
- ~~~~~~~~~~~~~~~
- 子线程执行完毕
- 主线程执行完毕
- ~~~~~~~~~~~~~~~
- 子线程执行完毕
- 主线程执行完毕
- ~~~~~~~~~~~~~~~
- 子线程执行完毕
- 主线程执行完毕
- ~~~~~~~~~~~~~~~
结果分析: 子线程每次都在主线程之前执行完毕, 即子线程会在主线程之前执行.
代码中循环 5 次是为了排除线程执行的随机性, 若不能说明问题, 可以调大循环次数进行测试.
2. 原理分析
查看 Thread 类源码:
- public final void join() throws InterruptedException {
- // 当调用 join()时, 实际是调用 join(long)方法
- join(0);
- }
查看 Join(long)方法源码:
- 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) { // 由于上一步传入参数为 0, 因此调用当前判断
- while (isAlive()) { // 判断子线程是否存活
- wait(0); // 调用 wait(0)方法
- }
- } else {
- while (isAlive()) {
- long delay = millis - now;
- if (delay <= 0) {
- break;
- }
- wait(delay);
- now = System.currentTimeMillis() - base;
- }
- }
- }
查看 isAlive()方法源码:
- /**
- * Tests if this thread is alive. A thread is alive if it has
- * been started and has not yet died.
- * 测试线程是否还活着. 如果线程存活的话它就是已经开始, 还没有死亡的状态.
- * @return <code>true</code> if this thread is alive;
- * <code>false</code> otherwise.
- */****
- public final native boolean isAlive();
说明: 该方法为本地方法, 判断线程对象是否存活, 若线程对象调用了 start()方法, 在没有死亡的情况下此判断为 true. 在上述例子中, 由于调用了子线程的 start()方法, 并且没有结束操作, 因此判断 true.
查看 wait()方法源码:
public final native void wait(long timeout) throws InterruptedException;
说明: 该方法为本地方法, 调用此方法的当前线程需要释放锁, 并等待唤醒. 在上述例子中, 主线程调用子线程对象的 join()方法, 因此主线程在此位置需要释放锁, 并进行等待.
- wait()与 wait(0)的区别:
- 查看 wait()方法源码, wait()方法只调用了 wait(0), 如下:
- public final void wait() throws InterruptedException {
- wait(0);
- }
- 因此, wait()与 wait(0)相同.
我们来撸一撸上述步骤: 主线程 wait()等待, 子线程调用了 run()执行, 打印 "子线程执行完毕". 此时, 主线程还没被唤醒, 还没有执行下面的操作. 那么, 问题来了, 谁? 在什么时候? 唤醒了主线程呢?
查看 Thread 类中存在 exit()方法, 源码如下:
- /**
- * This method is called by the system to give a Thread
- * a chance to clean up before it actually exits.
- * 这个方法由系统调用, 当该线程完全退出前给它一个机会去释放空间.
- */
- private void exit() {
- if (group != null) { // 线程组在 Thread 初始化时创建, 存有创建的子线程
- group.threadTerminated(this); // 调用 threadTerminated()方法
- group = null;
- }
- /* Aggressively null out all reference fields: see bug 4006245 */
- target = null;
- /* Speed the release of some of these resources */
- threadLocals = null;
- inheritableThreadLocals = null;
- inheritedAccessControlContext = null;
- blocker = null;
- uncaughtExceptionHandler = null;
- }
通过 debug,exit()在线程执行完 run()方法之后会被调用, 此时线程组中存在当前子线程, 因此会调用线程组的 threadTerminated()方法. 查看 ThreadGroup.threadTerminated()方法源码:
- /** Notifies the group that the thread {@code t} has terminated.
- * 通知线程组, t 线程已经终止.
- *
- void threadTerminated(Thread t) {
- synchronized (this) {
- remove(t); // 从线程组中删除此线程
- if (nthreads == 0) { // 当线程组中线程数为 0 时
- notifyAll(); // 唤醒所有待定中的线程
- }
- if (daemon && (nthreads == 0) &&
- (nUnstartedThreads == 0) && (ngroups == 0))
- {
- destroy();
- }
- }
- }
通过此方法, 将子线程从线程组中删除, 并唤醒其他等待的线程. 在上述例子中, 此时子线程被销毁, 并释放占用的资源, 并唤醒等待中的线程. 而在 join()方法中等待的主线程被唤醒, 并获得锁, 打印 "主线程执行完毕".
3. 总结
Join()方法, 使调用此方法的线程 wait()(在例子中是 main 线程), 直到调用此方法的线程对象 (在例子中是 MyThread 对象) 所在的线程 (在例子中是子线程) 执行完毕后被唤醒.
由于线程的启动与销毁其实是由操作系统进行操作, 所以在描述的时候刻意略去, 如果有疑惑的地方, 可以查看 C++ 编写的本地方法.
来源: https://juejin.im/post/5b3054c66fb9a00e4d53ef75