join()方法的使用

一、join()方法的使用

主线程创建并启动子线程,如果自线程中要进行大量的耗时运算,主线程往往将早于子线程结束之前结束。如果主线程想等待子线程执行完成之后再结束,比如子线程处理一个数据,主线程要取得这个数据中的值,就要用到 join() 方法

先来看一个不用 join() 方法的例子

public class MyThread extends Thread {

    @Override
    public void run() {
        try {
            int secondVal = 2000;
            System.out.println(secondVal);
            Thread.sleep(secondVal);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println("我想当 myThread 对象执行完毕之后我在执行");
        System.out.println("但上面代码中的 sleep 中的值应该写多少呢?");
        System.out.println("答案是:不能确定");
    }

}

结果是:

我想当 myThread 对象执行完毕之后我在执行
但上面代码中的 sleep 中的值应该写多少呢?
答案是:不能确定
run...

从结果看到,主线程 Main 在子线程 Thread-0 执行完之前就已经执行结束了,如果我们想要让主线程 Main 在子线程 Thread-0 执行结束之后在执行,join() 就派上用场了

public class MyThread1 extends Thread {

    @Override
    public void run() {
        try {
            int time = 2000;
            System.out.println(Thread.currentThread().getName() + " 执行:"
                    + time / 1000 + "s");
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyThread1 myThread1 = new MyThread1();
        myThread1.start();
        myThread1.join();
        System.out.println("我想当 myThread 对象执行完毕之后我在执行");
        System.out.println(System.currentTimeMillis());
    }
}

结果是:

Thread-0 执行:2s
我想当 myThread 对象执行完毕之后我在执行

从结果看到,线程 Thread-0,即子线程先执行了,而主线程 Main 在子线程执行完才执行。join() 方法使调用这个方法的线程 Thread-0 正常执行 run() 方法,而使 Thread-0 所在的线程 main 进行无限期的阻塞,等待线程 Thread-0 销毁后(执行完 run 方法后)再继续执行线程 main 后面的代码

因此,join() 方法使得调用该方法的那段代码所在的线程暂时阻塞

二、join()方法和sleep()方法的区别

Thread.sleep(long) 方法的作用是让当前执行的线程休眠,当前执行的线程指的是 Thread.currentThread() 返回的线程,据JDK API的说法,“该线程不丢失任何监视器的所属权”,简单说就是 sleep 代码上下文如果被加锁了,锁依然在,但是CPU资源会让出给其他线程。简单的说,就是 sleep 方法并不释放锁,但是会让出 CPU 资源

class ThreadB2 extends Thread {

    @Override
    public void run() {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + " beg "
                    + System.currentTimeMillis());

            System.out.println(Thread.currentThread().getName() + " end "
                    + System.currentTimeMillis());
        }
    }
}

class ThreadA2 extends Thread {

    private ThreadB2 threadB2;

    public ThreadA2(ThreadB2 threadB2) {
        this.threadB2 = threadB2;
    }

    @Override
    public void run() {
        synchronized (threadB2) {
            System.out.println(Thread.currentThread().getName() + " beg "
                    + System.currentTimeMillis());
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " end "
                    + System.currentTimeMillis());
        }
    }
}

public class Run2 {

    public static void main(String[] args) {
        ThreadB2 threadB2 = new ThreadB2();
        threadB2.setName("BBB");
        ThreadA2 threadA2 = new ThreadA2(threadB2);
        threadA2.setName("AAA");

        threadA2.start();
        threadB2.start();
    }

}

结果是:

AAA beg 1540712329189
AAA end 1540712333189
BBB beg 1540712333189
BBB end 1540712333189

从结果可以看到,线程 AAA 先执行 run 方法中的语句,等到线程 AAA 的 run 方法执行结束后,线程 BBB 才可以执行自己 run 方法中的代码
由于两个线程持有同一个对象锁 threadB2,因此当一个线程持有对象锁的时候,如果该线程没有释放锁,那么另一个线程也没法执行同步块中的方法。当线程 AAA 输出第一条语句后,第二条语句在 4s 之后才开始执行,这说明对象锁一直被线程 AAA 持有,即 Thread.sleep(long) 方法是不释放对象锁的

那么对于 join() 方法呢?我们把上面的代码稍作修改,只将 ThreadA2 中的 run 方法修改一番

class ThreadB2 extends Thread {

    @Override
    public void run() {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName() + " beg "
                    + System.currentTimeMillis());

            System.out.println(Thread.currentThread().getName() + " end "
                    + System.currentTimeMillis());
        }
    }
}

class ThreadA2 extends Thread {

    private ThreadB2 threadB2;

    public ThreadA2(ThreadB2 threadB2) {
        this.threadB2 = threadB2;
    }

    @Override
    public void run() {
        synchronized (threadB2) {
            System.out.println(Thread.currentThread().getName() + " beg "
                    + System.currentTimeMillis());
            try {
                System.out.println("wait之前:" + threadB2.isAlive());
                threadB2.join();
                System.out.println("wait 之后" + threadB2.isAlive());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " end "
                    + System.currentTimeMillis());
        }
    }
}

public class Run2 {

    public static void main(String[] args) {
        ThreadB2 threadB2 = new ThreadB2();
        threadB2.setName("BBB");
        ThreadA2 threadA2 = new ThreadA2(threadB2);
        threadA2.setName("AAA");

        threadA2.start();
        threadB2.start();
    }

}

结果是:

AAA beg 1540817368955
wait之前:true
BBB beg 1540817368956
BBB end 1540817368956
wait之后:false
AAA end 1540817368956

单从结果来看,线程 AAA 执行了一半,就停止了,然后线程 BBB 在执行,最后线程 AAA 执行 join() 方法后面的代码。如果不直到 join() 方法的实现的话,也能根据结果推荐,join() 方法是释放锁,而且和 wait() 方法的作用类似,因为 wait() 方法已经在这篇(https://blog.csdn.net/babycan5/article/details/83513832)讲得比较详细,所以这里我只从 join() 源码的来看(虽然也是使用 wait 来实现的…)

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");
    }
	
    //语句1
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

这下看的更明显了,这不还是使用 wait() 方法嘛!!!注意上面的语句1,它省略了一部分,我依据这个例子把缺的补上,然后分析。这里只需要注意两点, while 语句中的 isAlive() 到底是谁调用的?和下面的 wait(0) 是谁调用的?

例子中使用 threadB2.join() ,根据上面的例子,对象 threadB2 是对象锁,线程 AAA 持有该对象锁,首先在 while 的判断中执行 threadB2.isAlive(),即判断 threadB2 对应的线程 BBB 是否存活,如果线程 BBB 还活着,就用锁对象 threadB2 调用 wait() 方法,即 threadB2.wait(0)该锁对象被释放,同时,拥有 thradB2 锁对象的那个线程 AAA 进入等待状态 (很重要!!!),之后,线程 BBB 拿到对象锁,执行自己同步方法块中的代码,等到线程 BBB 执行结束,系统会自动调用 threadB2.notifyAll(),唤醒正在等待对象锁的线程 AAA,然后线程 AAA 才可以继续执行后面的代码

需要注意我加粗的那段文字,足以解释上面那个例子的结果了

因此,对比 sleep() 和 join() 方法,得出一个结论

  • Thread.sleep(long) 方法不释放锁,只是会暂时让出 CPU 资源,如果在同步方法中使用该方法,那么线程依然是同步执行的
  • join(long) 方法是会释放锁的,因为底层使用 wait() 方法来实现

三、参考

https://mp.weixin.qq.com/s/-VVlaSj0In1rDMN7thT6dQ

  • 20
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值