线程之间的协作就跟人与人之间的协作一样,有时候我们需要等待别人完成某项任务后,我们再来开始工作。有时候会出现一个线程的输入非常依赖另一个或多个线程的输出,此时这个线程就需要等待依赖线程执行完毕才能继续执行。在Thread类中有join()方法具有这样的功能:
第三个方法是无限等待,他会一直阻塞当前线程,直到目标线程执行完毕。前两个方法就定义了一个最大等待时间,时间过了,当前线程“等不及了”会继续往下执行。要注意的是join()的本质是让调用线程wait()在当前线程对象的实例上(具体wait()方法的介绍可以参看:https://blog.csdn.net/Dongguabai/article/details/82230279)。join()方法的源码如下:
这里有一个代码示例:
package dgb.test.concurrent;
import lombok.extern.slf4j.Slf4j;
/**
* @author Dongguabai
* @date 2018/8/31 15:17
*/
@Slf4j
public class JoinTest {
private static volatile int i = 0;
static class Thread1 extends Thread{
@Override
public void run() {
log.info("Thread1线程开始运行!!!");
for (i=0;i<10000000;i++);
log.info("Thread1线程运行结束!!!");
}
}
public static void main(String[] args) throws InterruptedException {
log.info("主线程开启-------------");
Thread1 t1 = new Thread1();
t1.start();
t1.join();
log.info("主线程结束-------------{}",i);
}
}
运行结果:
在运行结果的最后输出的i是10000000,证明了join()方法的效果,因为假如这里没有join()方法,在main()中输出的i应该为0或者特别小。
再简单分析一下main()中代码的执行流程:
有一个关键的地方就是thread1.wait()后,主线程会进入了thread1的等待队列中,在thread1执行完成后,会调用thread1.notify()方法,释放所有等待线程继续执行:
因此建议在程序中不要在Thread对象实例上使用类似wait()或者notify()等方法,因为这个很有可能影响系统API的工作或者被系统的API锁影响。
再来看一个例子,就很好理解了:
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread1执行---");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread1结束---");
}
},"threard1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread2执行---");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2结束---");
}
},"threard2");
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread3执行---");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread3结束---");
}
},"threard3");
thread1.start();
thread1.join();
thread2.start();
thread2.join();
thread3.start();
}
执行结果:
再看一个例子:
package com.example.threaddesign;
import java.util.stream.IntStream;
/**
* @author Dongguabai
* @date 2018/12/4 14:17
*/
public class ThreadJoin {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
IntStream.range(1,10).forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));},"t1");
Thread t2 = new Thread(()->{
IntStream.range(1,10).forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("main 线程开始了----");
IntStream.range(1,10).forEach(i-> System.out.println(Thread.currentThread().getName()+"->"+i));
}
}
要注意的是这个例子中,t1 和 t2 线程还是交替执行的,因为是 mian 线程去等待 t1 和 t2 执行完成。
运行结果:
t1->1
t2->1
t1->2
t2->2
t1->3
t2->3
t1->4
t2->4
t1->5
t2->5
t1->6
t2->6
t1->7
t2->7
t1->8
t2->8
t1->9
t2->9
main 线程开始了----
main->1
main->2
main->3
main->4
main->5
main->6
main->7
main->8
main->9
Process finished with exit code 0
再比如经常会有这样的一个面试题,需要在几个线程结束后计算这几个线程执行的时间,这样使用 join() 就很方便了,只需要保证工作线程 join() 在计时线程上就行了。
package com.example.threaddesign;
/**
* @author Dongguabai
* @date 2018/12/4 14:40
*/
public class ThreadJoin3 {
public static void main(String[] args) throws InterruptedException {
System.out.println("计时线程开始干活了。。。");
long startTime = System.currentTimeMillis();
Thread t1 = new Thread(new Task(1000));
Thread t2 = new Thread(new Task(1500));
Thread t3 = new Thread(new Task(2000));
t1.start();
t2.start();
t3.start();
t1.join();
t2.join();
t3.join();
long endTime = System.currentTimeMillis();
System.out.printf("全部线程工作结束,总共耗时::[%s]",endTime-startTime);
}
}
class Task implements Runnable{
//工作耗时
private long time;
public Task(long time) {
this.time = time;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 开始干活了");
try {
Thread.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 干活结束");
}
}
输出结果:
计时线程开始干活了。。。
Thread-0 开始干活了
Thread-1 开始干活了
Thread-2 开始干活了
Thread-0 干活结束
Thread-1 干活结束
Thread-2 干活结束
全部线程工作结束,总共耗时::[2006]