Java线程基础(14): join()

目录

Java中如何让多线程按照自己指定的顺序执行?

1、join()的使用场景

2、join对于线程的作用​

3、join()方法:

4、join() 源码分析

5、join使用时注意几点:

1、join与start调用顺序问题

2、join()与异常

6、什么时候会使用Thread.join


Java中如何让多线程按照自己指定的顺序执行?

        这个问题最简单的回答是通过Thread.join来实现,久而久之就让很多人误以为Thread.join是用来保证线程的顺序性的。

1、join()的使用场景

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

2、join对于线程的作用

3、join()方法:

join()是Thread类的一个方法

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

4、join() 源码分析

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

Join方法实现是通过wait(小提示:Object 提供的方法)。 当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。

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,同步的对象是当前线程实例。

(2) 无参版本和两个参数版本最终都调用了一个参数的版本。

(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() 会一直保持锁。

5、join使用时注意几点:

1、join与start调用顺序问题

  上面的讨论大概知道了join的作用了,那么,如果 join在start前调用,会出现什么后果呢?先看下面的测试结果

​
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方法调用之后调用才有意义。
这个也很容易理解:如果一个线程都没有start,那它也就无法同步了。

2、join()与异常

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

如下:threadB中启动threadA,并且调用其方法等待threadA完成,此时向threadB发出中断信号,会进入中断异常代码。

public class TestDemo {
    public static void main(String[] args) throws InterruptedException {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        BThread bt = new BThread();
//        try {
            Thread.sleep(2000);
            Thread.currentThread().interrupt();
            bt.start();
            bt.join();
            
//        } catch (Exception e) {
//            System.out.println("Exception from main");
//        }
        System.out.println(threadName + " end!");
    }
}
执行结果:
main start.
[BThread] Thread start.
[BThread] Thread loop at 0
Exception in thread "main" java.lang.InterruptedException
    at java.lang.Object.wait(Native Method)
    at java.lang.Thread.join(Thread.java:1249)
    at java.lang.Thread.join(Thread.java:1323)
    at com.dxz.join.TestDemo.main(TestDemo.java:46)
[BThread] Thread loop at 1
[BThread] Thread loop at 2
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread end.

6、什么时候会使用Thread.join

在实际应用开发中,我们很少会使用thread.join。在实际使用过程中,我们可以通过join方法来等待线程执行的结果,其实有点类似future/callable的功能。
我们通过以下伪代码来说明join的使用场景、

public void joinDemo(){
   //....
   Thread t=new Thread(payService);
   t.start();
   //.... 
   //其他业务逻辑处理,不需要确定t线程是否执行完
   insertData();
   //后续的处理,需要依赖t线程的执行结果,可以在这里调用join方法等待t线程执行结束
   t.join();
}

参考:Thread之三:Thread Join()的用法 - duanxz - 博客园

【阿里面试系列】Thread.join的作用 - 知乎

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值