技术学习笔记
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode

3.4join方法

问题

子线程需要进行大量运算,主线程早于子线程退出。那么主线程无法获取子线程执行后的信息。

『解决方法』主线程中调用子线程的 join,使得在子线程运行时,主线程进入 waiting 状态。join 之后主线程原地等待子线程销毁。

MyThread.java
public class MyThread extends Thread{
    @Override
    public void run(){
        try{
            long time = (long) (Math.random() * 10000);
            System.out.println("time:" + time);
            Thread.sleep(time);
        }catch(Exception e){

        }
    }
}
Run.java
public class Run {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();

        try {
            t.join();//使得主线程被无限期阻塞,等待子线程销毁
        } catch (Exception e){

        }
        System.out.println("can");
        System.out.println("you");
    }
}

join 之后,线程的 interrupt() 方法可以引发 InterruptedException

ThreadA.java
public class ThreadA extends Thread{
    @Override
    public void run(){
        try {
            for(int i = 0; ; i++){
                String str = new String("fine");
                if (isInterrupted()){
                    throw new InterruptedException();
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }

    }
}
ThreadB.java
public class ThreadB extends Thread{
    ThreadA a;
    @Override
    public void run(){
        try{
            a = new ThreadA();
            a.start();
            a.join();
            System.out.println("线程B继续执行");
        }catch (InterruptedException e){
            System.out.println("线程B被中断");
            e.printStackTrace();
            a.interrupt();
        }
    }
}
Run.java
public class Run {
    public static void main(String[] args) {
        ThreadB b = new ThreadB();
        b.start();
        b.interrupt();
    }
}

默认情况下,interrupt() 方法不会引起线程异常,像线程A。而像线程 B 有调用了它的 join(),再调用 interrupt() 方法就会引起线程的中断异常。

运行结果

join(long) 与 sleep(long) 的区别

  • join 内部使用 wait() 来实现,所以会释放锁,允许其他线程进入同步区域。
  • sleep 不会释放锁。
  • join 之后主线程原地等待子线程销毁。
  • 主线程调用 wait 需要获取子线程 t 的对象锁,因为 t.join() 涉及到 wait() 和同步。
ThreadA.java
public class ThreadA extends Thread{
    ThreadB b;
    public ThreadA(ThreadB b){
        this.b = b;
    }
    @Override
    public void run(){
        try {
           synchronized (b){
               b.start();
               b.join(6000);//释放了当前线程对 b 的锁
               while(true){
               }
           }
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

执行结果

B 开始
B 服务
B 结束

join 后面的内容提前执行

ThreadA.java
public class ThreadA extends Thread{
    ThreadB b;
    public ThreadA(ThreadB b){
        this.b = b;
    }
    @Override
    public void run(){
        try {
           synchronized (b){
               System.out.println("A start");
               Thread.sleep(5000);//不释放锁,主线程无法访问 b
               //b.wait();//会释放锁
               System.out.println("A end");
           }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
ThreadB.java
public class ThreadB extends Thread{

    @Override
    synchronized public void run(){
        try{
            System.out.println("B start");
            Thread.sleep(6000);
            System.out.println("B end");
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
Run.java
public class Run {
    public static void main(String[] args) {
        ThreadB b = new ThreadB();
        ThreadA a = new ThreadA(b);

        try{
            a.start();//一旦线程 a 进入 run,会占有 b 锁并且不释放。
            b.start();
            b.join(4000);//这一行比前面两个 start 都要早执行,主线程阻塞等待 b 线程 4000ms。
            System.out.println("Main end");
        }catch (InterruptedException e){

        }
    }
}

执行顺序

运行结果有三种可能

  • b.join(4000) 先拿到锁并且很快释放。
  • a 拿到锁,开始执行,不释放锁。持续 5000ms。
  • b.join(4000) 比线程 b 先拿到锁,但很快又释放,但此时已经过了 4000ms ,于是主线程不再阻塞,继续执行打印 Main end。
  • b.start() 拿到锁就开始执行,不释放锁。持续 6000ms。

A start
A end
Main end
B start
B end

  1. 和第一种情况一样,只是 ThreadB 在争夺锁的时候比 Main end 的打印快了一点。
  • Main end 和『B 开始』异步打印,Main end 慢了一点。

A start
A end
B start
Main end
B end

  • b.join(4000) 先拿到锁并且很快释放。
  • a 拿到锁,开始执行,不释放锁。持续 5000ms。
  • 线程 b 比 b.join(4000) 先拿到锁,不释放锁,持续 6000ms。然后输出『结束』
  • b.join(4000) 再次拿到锁。时间已经超过 4000ms, 主线程不再等待,打印 Main end。

A start
A end
B start
B end
Main end