【多线程并发编程】十 线程的join

程序猿学社的GitHub,欢迎Star
https://github.com/ITfqyd/cxyxs
本文已记录到github,形成对应专题。

前言

学过数据库的社友,应该都使用过join,当然,本文讲的join,是线程的join方法。

结论

先给出结论,join会堵塞当然调用的线程,直到拿到结果后,才会继续执行当前调用的那个线程。

  • 回想起,19年的一个大冬天,凌晨2点,高铁转火车,火车还晚点2小个小时,坐过火车的人,应该都知道,火车的人数是真的多,所以,我早早的开始排队,大约从第50个,变成100,200,300。join可以理解为,就是这些插队的人,我很急,急着回家。当然,插队的行为,真的十分的不文明。

场景

有个别社友就在问,这个学了以后,能否派上用处,对此,我只能说,这个社友还是蛮务实的,实际上,很多的东西,不一定需要派上用场,才去学习他,就是为了扩充我们的视野,把我们的基础打牢固。我一直坚信的一个理念,存在便有一定的道理

  • 有不少做过导出的朋友,可以发现,有时候大数据量导出很慢,又不少朋友在想办法看看能否缩短这个时间。这里我们就可以使用多线程导出,通过join获取结果,再把结果合并,例如一页10w条,你查下出来需要20s,你设计成10页,起10个线程,是不是1次只用查询1w条,是不是大大的节约了时间。
  • 一般需要等子线程j的运行结果后,主线程才继续运行的场景,我们都可以使用join。

实战

不加join代码

package com.cxyxs.thread.nine;

/**
 * Description:转发请注明来源  程序猿学社 - https://ithub.blog.csdn.net/
 * Author: 程序猿学社
 * Date:  2020/2/28 10:21
 * Modified By:
 */
public class JoinThread implements  Runnable{
    @Override
    public void run() {
        System.out.println("子线程在运行中!");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("子线程已运行完!");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new JoinThread());
        thread.start();
        //thread.join();
        Thread.sleep(100);
        System.out.println("main方法已运行完!");
    }
}

不适用join大家猜猜,输出到控制台的是什么结果?
在这里插入图片描述

  • sleep休眠100毫秒,也是为了保证子线程先运行。不然,这个结果很难确定谁先,谁后。
  • 首先main方法运行,main方法休眠100ms,子线程就抢到时间片,打印一句话,子线程休眠1秒,main方法抢到时间片,继续运行。

增加join代码

 public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new JoinThread());
        thread.start();
        thread.join();
        Thread.sleep(100);
        System.out.println("main方法已运行完!");
    }

增加一个join,大家觉得,他的输出结果应该是什么(JoinThread类的代码省略点)?
在这里插入图片描述

  • 我们可以发现,先运行完后子线程的所有业务后,才开始运行main后面的逻辑代码。所以join,我们可以理解为插队加塞。在main线程里面调用,就会堵塞main线程。一直到返回结果后,才继续运行main线程。

看到这里,又有社友提出一个疑问,会不会堵塞其他的子线程。我们再增加一个子线程试试。

增加join代码,模拟两个子线程

 public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
                System.out.println("t1线程正在工作中");
        });

        Thread thread = new Thread(new JoinThread());
        thread.start();
        //这句代码很关键
        thread.join();
        t1.start();
        Thread.sleep(100);
        System.out.println("main方法已运行完!");
    }

在这里插入图片描述
出现这种结果后,很多社友纷纷给我说,社长,脸痛不,这打脸也太快了把。
为什么会出现这种结果?

  • thread线程启动后,就直接调用join方法,很明显会堵塞main线程,t1都没有启动,怎么去cpu中抢时间片,是不是很多社友,都觉得社长在狡辩。没关系,我们把join的位置变动一下,奇迹的事情,就会发生了,各位社友,睁大眼睛,好好看。

模拟两个子线程,移动join位置

public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
                System.out.println("t1线程正在工作中");
        });

        Thread thread = new Thread(new JoinThread());
        thread.start();
        t1.start();
        //这句代码很关键
        thread.join();
        Thread.sleep(100);
        System.out.println("main方法已运行完!");
    }

在这里插入图片描述

  • 通过上图的测试结果,我们可以发现,堵塞的只是当前线程,也就是我们的main线程(调用方),依次,我们可以下这个结论,谁调用谁堵塞,不会影响其他的线程正常的运行。是不会影响其他的子线程正常的运行的。这也是不少人存在的一个误区。

源码分析

通过上面的demo,我们对join方法的使用,有了一个大致的认识。但是,他底层的代码怎么一回事,话不多说,上源码。
我们大部分的时候,使用的都是不带参数的调用join

public final void join() throws InterruptedException {
        join(0);
    }
 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方法,不指定参数,默认就是0,0标识没有设置超时时间会一直等待。
  • 大于0,会判断线程,在规定的时间内,是否执行完,没有执行完,也不会等待。
  • isAlive方法,判断线程是否活着。
  • join方法实际上就是调用wait方法实现线程的堵塞。知道调用notify才会被唤醒。
  • join函数用了synchronized关键字,表示线程安全。
    很多的社友,很困惑,在mian方法中调用另外一个线程的join方法,为什么,main线程会进入等待状态,直到另外一个线程运行完后,才开始运行。
    通过查看源码,我们可以知道,调用join,实际上就是调用wait,我们都知道wait是与synchronized成对存在的。也就说让持有这个锁的线程进入等待队列中,那是谁进入等待队列?
    查看join的源码,我们可以发现他就是一个同步代码块,main线程调用join。所有main线程会进入等待队列中 。
    main线程为什么等待一段时间后,又可以继续运行?
    这是因为,再main线程中,调用的另外一个线程,结束以后,会调用notifyAll方法,从而我们的main线程,才能继续运行。
发布了286 篇原创文章 · 获赞 557 · 访问量 22万+

猜你喜欢

转载自blog.csdn.net/qq_16855077/article/details/104548411