欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

多线程基础(三) -- join方法详解,join阻塞的到底是什么线程

程序员文章站 2022-07-10 21:08:07
理解join()方法之前请确保对wait()/notify()/notifyAll()机制已熟练掌握。join()方法的作用是等待线程销毁。join()方法反应的是一个很现实的问题,比如main线程的执行时间是1s,子线程的执行时间是10s,但是主线程依赖子线程执行完的结果,这时怎么办?可以像生产者/消费者模型一样,搞一个缓冲区,子线程执行完把数据放在缓冲区中,通知main线程,main线程去拿,这样就不会浪费main线程的时间了。另外一种方法,就是join()了。例一:阻塞mian线程public...

理解join()方法之前请确保对wait()/notify()/notifyAll()机制已熟练掌握。

join()方法的作用是等待线程销毁。

join()方法反应的是一个很现实的问题,比如main线程的执行时间是1s,子线程的执行时间是10s,但是主线程依赖子线程执行完的结果,这时怎么办?可以像生产者/消费者模型一样,搞一个缓冲区,子线程执行完把数据放在缓冲区中,通知main线程,main线程去拿,这样就不会浪费main线程的时间了。另外一种方法,就是join()了。

例一:阻塞mian线程

public class MyThread36 extends Thread {
    public void run() {
        try {
            int secondValue = (int)(Math.random() * 10000);
            System.out.println(secondValue);
            Thread.sleep(secondValue);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public static void main(String[] args) throws Exception {
    MyThread36 mt = new MyThread36();
    mt.start();
    mt.join();
    System.out.println("我想当mt对象执行完毕之后我再执行,我做到了");
}

看一下运行结果:

3111
我想当mt对象执行完毕之后我再执行,我做到了

意思是,join()方法会使调用join()方法的线程(也就是mt线程)所在的线程(也就是main线程)无限阻塞,直到调用join()方法的线程销毁为止,此例中main线程就会无限期阻塞直到mt的run()方法执行完毕。

例二:不能阻塞同级线程

如果大家对于上述的例子还有疑问:
比如有没有可能是2个并行线程,执行join方法阻塞另一个?

看一个例子:

public class TestJoin implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("vip来了" + i + Thread.currentThread().getName());
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin = new TestJoin();
        Thread a = new Thread(testJoin, "a");
        Thread b = new Thread(testJoin, "b");
        a.start();
        b.start();
        a.join();
        System.out.println("我是main,我完了!!!");
    }
}

结果:

vip来了0b
vip来了0a
vip来了1b
vip来了1a
vip来了2b
vip来了2a
vip来了3b
vip来了3a
vip来了4a
vip来了4b
我是main,我完了!!!

如果按上述想法,很明显,应该等a执行完,再执行b,很明显结果并不是这样。
在看一个例子,证明是调用方也就是父线程被阻塞:

例三:阻塞调用方线程

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

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");
        }
    }
}
class AThread extends Thread {
    BThread bt;
    public AThread(BThread bt) {
        super("[AThread] Thread");
        this.bt = bt;
    }
    public void run() {
        String threadName = Thread.currentThread().getName();
        System.out.println(threadName + " start.");
        try {
            bt.join();
            System.out.println(threadName + " end.");
        } catch (Exception e) {
            System.out.println("Exception from " + threadName + ".run");
        }
    }
}

打印结果:

main start.
[BThread] Thread start. // 父线程B启动执行
[BThread] Thread loop at 0
[BThread] Thread loop at 1
[AThread] Thread start. // 2秒后main线程睡眠结束,启动at线程,at线程被bt.join阻塞
[BThread] Thread loop at 2
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread end.
[AThread] Thread end. // bt线程执行完,at线程开始执行
main end! // 由于调用了at.join,所以只能等待at线程执行完再执行at的父线程main线程

做一些修改,将上述的main方法中的at.join()注释,查看运行结果:

main start.
[BThread] Thread start. // bt线程启动
[BThread] Thread loop at 0
[BThread] Thread loop at 1
main end! // main线程由于没有被阻塞,所以和其他线程并行执行。此时main线程已经执行结束
[AThread] Thread start. // at线程启动,由于被bt.join阻塞,只能等bt线程执行完执行
[BThread] Thread loop at 2
[BThread] Thread loop at 3
[BThread] Thread loop at 4
[BThread] Thread end.
[AThread] Thread end.// at线程执行,

从这个例子可以看出来,阻塞的是调用join方法的线程的调用线程。

和sleep的区别:

join()方法的一个重点是要区分出和sleep()方法的区别。join(2000)也是可以的,表示调用join()方法的线程所在的线程最多等待2000ms,也就是比如main线程只会被阻塞2000毫秒。
两者的区别在于:sleep(2000)不释放锁,join(2000)释放锁,因为join()方法内部使用的是wait(),因此会释放锁。看一下join(2000)的源码就知道了,join()其实和join(2000)一样,无非是join(0)而已:

public final synchronized void join(final long millis) throws InterruptedException {
    if (millis > 0) {
        if (isAlive()) {
            final long startTime = System.nanoTime();
            long delay = millis;
            do {
                wait(delay);
            } while (isAlive() && (delay = millis -
                    TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime)) > 0);
        }
    } else if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        throw new IllegalArgumentException("timeout value is negative");
    }
}

join与异常

如果join过程中,当前线程对象被中断,则当亲啊线程出现异常

参考:https://blog.csdn.net/sinat_29384657/article/details/52228578
https://www.cnblogs.com/xrq730/p/4851233.html

本文地址:https://blog.csdn.net/weixin_39724194/article/details/107362564