首页 > 其他分享 >Thread join()的使用场景和原理

Thread join()的使用场景和原理

时间:2024-04-02 19:56:25浏览次数:17  
标签:BThread 场景 join Thread terminate 线程 interrupt

1、使用场景

  一般情况下,主线程创建并启动子线程,如果子线程中执行大量耗时运算,主线程可能早于子线程结束。如果主线程需要知道子线程的执行结果时,就需要等待子线程执行结束。主线程可以sleep(xx),但这样的xx时间不好确定,因为子线程的执行时间不确定,join()方法比较合适这个场景。

2、join()方法定义和使用

  join()是Thread类的一个方法。根据jdk文档的定义:

  public final void join()throws InterruptedException: Waits for this thread to die.

  join()方法的作用,是等待这个线程结束;这个定义比较模糊,个人认为"Java 7 Concurrency Cookbook"的定义较为清晰:

Waiting for the finalization of a thread

In some situations, we will have to wait for the finalization of a thread. For example, we may have a program that will begin initializing the resources it needs before proceeding with the rest of the execution. 
We can run the initialization tasks as threads and wait for its finalization before continuing with the rest of the program.
For this purpose, we can use the join() method of the Thread class. When we call this method using a thread object, it suspends the execution of the calling thread until the object called finishes its execution.

  解释一下,是主线程等待子线程的终止。也就是说主线程的代码块中,如果碰到了t.join()方法,此时主线程需要等待(阻塞),等待子线程结束了(Waits for this thread to die.),才能继续执行t.join()之后的代码块。

                                      

   来看一个join()的案例demo:

public class JoinDemo {

    public static void main(String[] args) throws InterruptedException {
        //获取当前线程信息
       Thread previousThread= Thread.currentThread();
        for(int i=0;i<10;i++){
            Thread thread=new Thread(new Domino(previousThread));
            thread.start();
            previousThread=thread;
        }
        TimeUnit.SECONDS.sleep(5);
        System.out.println("Thread.currentThread().getName()+\" terminate.\" = " + Thread.currentThread().getName()+" terminate.");

    }

    static class Domino implements Runnable{
        private Thread thread;
        public Domino(Thread thread){
            this.thread=thread;
        }
        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " terminate.");
        }
    }
}

  这段demo的逻辑就是当前线程等待它前面的线程执行结束再执行,输出如下:

Thread.currentThread().getName()+" terminate." = main terminate.
Thread-0 terminate
Thread-1 terminate
Thread-2 terminate
Thread-3 terminate
Thread-4 terminate
Thread-5 terminate
Thread-6 terminate
Thread-7 terminate
Thread-8 terminate
Thread-9 terminate

3、源码分析

public final void join() throws InterruptedException {
    join(0);
}

  再看join(long millis)方法:

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;
            }
        }
    }

  isAlive() 是 Thread 内部的一个 native 方法,只要当线程处于 NEW 和 TERMINATED 状态时返回false,其余都返回true。join() 其实就是通过调用 wait() 方法(Object定义的,详见线程间通信),wait(0) 表示当前线程立即释放锁(这里的锁指的就是前一个线程)并进入 waiting 状态,等待其他线程唤醒(notify/notifyAll)。

  join() 一共有三个重载版本,分别是无参、一个参数、两个参数:

public final void join() throws InterruptedException;

 public final synchronized void join(long millis) throws InterruptedException;
 
 public final synchronized void join(long millis, int nanos) throws InterruptedException;

  (1) 三个方法都被final修饰,无法被子类重写。

  (2) join(long), join(long, long) 是synchronized method,同步的对象是当前线程实例。

  (3) join() 和 join(0) 是等价的,表示一直等下去;join(非0)表示等待一段时间。

  从源码可以看到 join(0) 调用了Object.wait(0),其中Object.wait(0) 会一直等待,直到被notify/中断才返回。

  while(isAlive())是为了防止子线程伪唤醒(spurious wakeup),只要子线程没有TERMINATED的,父线程就需要继续等下去。

  (4) join() 和 sleep() 一样,可以被中断(被中断时,会抛出 InterrupptedException 异常);不同的是,join() 内部调用了 wait(),会出让锁,而 sleep() 会一直保持锁。

4、join() 方法注意点

4.1、join() 与 start() 的调用顺序

package com.dxz.join;
class BThread extends Thread {
    public BThread() {
        super("[BThread] Thread");
    };
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        try {
            for (int i = 0; i < 5; i++) {
                System.out.println(threadName + " loop at " + i);
                Thread.sleep(1000);
            }
            System.out.println(threadName + " end.");
        } catch (Exception e) {
            System.out.println("Exception from " + threadName + ".run");
        }
    }
}

public class TestDemo {
    public static void main(String[] args) {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        BThread bt = new BThread();
        try {
            bt.join();
            bt.start();
            Thread.sleep(2000);
        } catch (Exception e) {
            System.out.println("Exception from main");
        }
        System.out.println(threadName + " end!");
    }
}

  执行结果:

main start.
[BThread] Thread start.
[BThread] Thread loop at 0
[BThread] Thread loop at 1
main end!
[BThread] Thread loop at 2
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread end.

  main线程没有等待 [BThread] 线程执行完再执行。join方法必须在线程start方法调用之后调用才有意义。这个也很容易理解:线程没有执行,isAlive() 方法就不会返回true,也就不会执行 wait(0) 方法了。

4.2、join() 与中断异常

  在join()过程中,如果当前线程被中断,则当前线程出现异常。(注意是调用thread.join()的线程被中断才会进入异常,比如a线程调用b.join(),a中断会报异常而b中断不会异常)

  下面总结下中断与 wait()/join()/sleep() 的使用:

  调用interrupt()方法,立刻改变的是中断状态,但如果不是在阻塞态,就不会抛出异常;如果在进入阻塞态后,中断状态为已中断,就会立刻抛出异常,但同时中断标志也会被系统复位。

(1)sleep() &interrupt()

  线程A正在使用sleep()暂停着: Thread.sleep(100000),如果要取消它的等待状态,可以在正在执行的线程里(比如这里是B)调用a.interrupt()[a是线程A对应到的Thread实例],令线程A放弃睡眠操作。即,在线程B中执行a.interrupt(),处于阻塞中的线程a将放弃睡眠操作。

  当在sleep中时线程被调用interrupt()时,就马上会放弃暂停的状态并抛出InterruptedException。抛出异常的,是A线程。

(2)wait() &interrupt()

  线程A调用了wait()进入了等待状态,也可以用interrupt()取消。不过这时候要注意锁定的问题。线程在进入等待区,会把锁定解除,当对等待中的线程调用interrupt()时,会先重新获取锁定,再抛出异常。在获取锁定之前,是无法抛出异常的。

(3)join() &interrupt()

  当线程以join()等待其他线程结束时,当它被调用interrupt(),它与sleep()时一样,会马上跳到catch块里.。

  注意,调用的interrupt()方法,一定是调用被阻塞线程的interrupt方法。如在线程a中调用线程t.interrupt()。

标签:BThread,场景,join,Thread,terminate,线程,interrupt
From: https://www.cnblogs.com/jing-yi/p/18111377

相关文章

  • Thread 类使用及常用操作
    Thread类是JVM用来管理线程的一个类,换句话说,每个线程都有一个唯一的Thread对象与之关联。每个执行流,也需要有一个对象来描述,类似下图所示,而Thread类的对象就是用来描述一个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。1Thread的常见构造方......
  • 执行计划】Oracle 11gR2使用Full outer Joins执行计划完成全外连接查询
    1.创建实验表并初始化实验数据sys@ora11g>select*fromv$version;BANNER------------------------------------------------------------------------OracleDatabase11gEnterpriseEditionRelease11.2.0.1.0-ProductionPL/SQLRelease11.2.0.1.0-ProductionCORE  ......
  • ES6 reduce方法:示例与详解、应用场景
    还是大剑师兰特:曾是美国某知名大学计算机专业研究生,现为航空航海领域高级前端工程师;CSDN知名博主,GIS领域优质创作者,深耕openlayers、leaflet、mapbox、cesium,canvas,webgl,echarts等技术开发,欢迎加底部微信(gis-dajianshi),一起交流。No.内容链接1Openlayers【入门教程】-......
  • 测量型激光雷达在交通领域的应用场景
    测量型激光雷达在交通领域的应用场景十分广泛。以下是一些主要的应用:车辆检测和计数:在高速公路收费站口,新式的车辆检测系统利用激光雷达实时检测来往车辆的轮廓,进行车辆计数和安全检测。这种系统不仅可以对超高、超宽、超长的车辆进行劝返,防止其进入高速公路,还可以同时兼顾车......
  • Threadx rtos 移植指南(stm32f1)
    Threadx系统移植非常简单,下面记录gnu工具链移植步骤库文件目录.├──cmake#CMakelistfilesforbuildingtheproject├──common#CoreThreadXfiles├──common_modules#CoreThreadXmodul......
  • 【码银送书第十六期】大模型在金融行业的应用场景和落地路径
    作者:林建明来源:IT阅读排行榜本文摘编自《AIGC重塑金融:AI大模型驱动的金融变革与实践》,机械工业出版社出版文章转自:大模型在金融行业的应用场景和落地路径 这是最好的时代,也是最坏的时代。尽管大模型技术在金融领域具有巨大的应用潜力,但其应用也面临不容忽视的风险和挑......
  • 2024年4月1日-简单场景绘制、打包验证资源
    先从导入的地图里,把地面复制过来 复制到默认地图 在材质里面找到光,可以修改光的强度 平整场地 用管理里面的删除和添加,弄出一大片平整的地形  把角色放到地图上 选到植物模式,把下载的素材包里的植物拖进去 然后随意丢一点素材到工程里 ......
  • rt-thread 设备驱动框架
    RT-Thread设备框架属于组件和服务层,是基于RT-Thread内核之上的上层软件。设备框架是针对某一类外设,抽象出来的一套统一的操作方法及接入标准,可以屏蔽硬件差异,为应用层提供统一的操作方法。RT-Thread设备框架分为三层:设备驱动层、设备驱动框架层、I/O设备管理层。其中设备驱......
  • guice的MethodInterceptor的用法和使用场景和设计思想
    Guice是一个轻量级的依赖注入框架,它也提供了AOP(面向切面编程)的功能,其中MethodInterceptor是Guice用来实现AOP的一个接口。MethodInterceptor的用法:MethodInterceptor接口是AOP联盟(AOPAlliance)的一部分,Guice使用这个接口来定义方法拦截器。拦截器可以在方法执行前后添加......
  • 阿里最新HomView-MOT技术:UAV动态场景下的多目标跟踪
    来源:3D视觉工坊添加小助理:dddvision,备注:方向+学校/公司+昵称,拉你入群。文末附行业细分群扫描下方二维码,加入3D视觉知识星球,星球内凝聚了众多3D视觉实战问题,以及各个模块的学习资料:近20门视频课程(星球成员免费学习)、最新顶会论文、计算机视觉书籍、优质3D视觉算法源码等。想要......