美文网首页多线程
谈谈join()方法

谈谈join()方法

作者: BestbpF | 来源:发表于2018-10-18 22:37 被阅读40次

简述

我们在使用多线程时,有时候会有这样的需求:

  • 主线程创建子线程并启动后,有可能子线程中存在比较耗时的操作(但耗时多少不确定),主线程往往会早于子线程结束,如果我们想要在子线程完成后再结束主线程呢?
  • 首先想到的是Thread.sleep(xxx),但我们无法确定应该睡眠多少时间
  • 而join()方法满足了我们的需求

源码

join()方法的作用是阻塞当前线程,直到调用join()的线程结束销毁,或者指定阻塞时长,若线程没停止但是超时,取消阻塞

    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;
            }
        }
    }
1、方法参数millis是指定join的时长(join()默认参数为0)
2、base是当join()方法开始执行的初始时间,now是方法运行的时长
3、当millis<0时,抛出非法参数异常
4、当millis=0时,当前线程无线等待,直到调用join()的线程停止销毁
5、当millis>0时,阻塞指定时长,为了避免被唤醒时间也不会出差错,
   需要循环计算运行时间now,当now >= millis时,表示时间已到,退出循环。

【例如】

ThreadA a = new ThreadA();
a.start();//假设a会执行50s
a.join();
System.out.println("我要在a执行完后打印");

a.join()会无限阻塞当前线程,直到a执行完毕并销毁,达到目的效果

【异常】
在join()过程中,如果当前线程被打断,会抛出异常

//伪码
ThreadB:
run(){
    ThreadA a = new ThreadA();
    a.start();
    a.join();
}

如果b线程在运行过程中被打断了,而a处于join状态,b会抛出InterruptedException异常,但是a不受影响

其他情况

上面我们了解到,join()能够做到安排线程执行顺序,但是这是一定的吗?或者说join后面的方法一定后执行吗?答案是否定的。
我们看到join方法的定义中有synchronized加持(因为内部使用的是wait()),因此有可能出现下面这种情况

  • 有两个线程,线程A和线程B,A中含有成员变量b,并且将其作为一个锁对象
public class ThreadA extends Thread {
    private ThreadB b;

    public ThreadA(ThreadB b) {
        super();
        this.b = b;
    }

    @Override
    public void run() {
        try {
            synchronized (b) {
                System.out.println("begin A ThreadName="
                        + Thread.currentThread().getName() + "  "
                        + System.currentTimeMillis());
                Thread.sleep(5000);
                System.out.println("  end A ThreadName="
                        + Thread.currentThread().getName() + "  "
                        + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 线程B的run方法被synchronized加持,意味着锁对象为当前对象本身
public class ThreadB extends Thread {
    @Override
    synchronized public void run() {
        try {
            System.out.println("begin B ThreadName="
                    + Thread.currentThread().getName() + "  "
                    + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println("  end B ThreadName="
                    + Thread.currentThread().getName() + "  "
                    + System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
  • 运行如下代码
    public static void main(String[] args) {
        try {
            ThreadB b = new ThreadB();
            ThreadA a = new ThreadA(b);
            a.start();
            b.start();
            b.join(2000);
            System.out.println("我要比B后执行哦!");//1
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

语句1是否能如愿在b之后打印呢?不一定!因为a线程,b线程,以及调用b.join(2000)的主线程,他们三个抢占的是同一把锁!有可能出现以下情况:

  1. b.join(2000)先抢占到锁,注意此时起始时间base已产生!然后wait(2000)释放锁
  2. a拿到锁,并执行完,花费了5s,此时b.join(2000)内部的wait(2000)也早执行完了,于是主线程和b线程又开始竞争锁!
  3. 很不幸主线程b.join(2000)又拿到了锁,但是和base一对比发现时间早过了,于是退出了循环
  4. 此时就剩b线程和语句1了,两者不存在竞争关系,所以打印顺序也可能出现多种,即主线程可能先于b线程打印语句1

相关文章

网友评论

    本文标题:谈谈join()方法

    本文链接:https://www.haomeiwen.com/subject/exqezftx.html