在很多场景中,有时,后启动的线程可能需要依赖先启动的线程执行完成才能正确的执行线程中的业务逻辑。此时,就需要确 保线程的执行顺序。保证线程顺序执行的方法有很多,如:Thread类中的join方法,CountDownLatch,Executors 类,CompletableFuture等,本文记录的是Thread类中的join方法。
1、确保线程顺序执行的示例
使用Thread
类中的
join()
方法来确保线程的执行顺序。例如,下面的测试代码。
package com.thread.sort;
public class ThreadSort {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> { System.out.println("thread1"); });
Thread thread2 = new Thread(() -> { System.out.println("thread2"); });
Thread thread3 = new Thread(() -> { System.out.println("thread3"); });
thread1.start();
thread1.join();
thread2.start();
thread2.join();
thread3.start();
thread3.join();
}
}
可以看到每个线程的启动方法后面增加了调用线程的join的方法,多次执行结果如下:
thread1
thread2
thread3
2、join方法保证执行顺序的原理
进入Thread类的join方法,代码如下:
public final void join() throws InterruptedException { join(0); }
可以看到在join方法中调用了本类中重写的有参的join方法,并且传递参数是0,继续跟进代码如下:
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;
}
}
}
从以上代码可以看出,join(long mills)方法使用了synchronized修饰,说明这个方法同一个时刻只能被同一个实例或者方法调用,由于参数是0,所以程序会进入以下处理逻辑
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
在代码中以while循环的方式来判断当前线程是否处于活跃状态,如果是,则调用同类中的wait()方法,并且传参为0,wait()方法处理逻辑如下:
public final native void wait(long timeout) throws InterruptedException;
该方法是一个本地方法,通过JNI的方式调用JDK底层的方法来使线程等待执行完成。需要注意的是,调用线程的wait()方法时,会使主线程处于等待的状态,等待子线程完成再继续向下执行,也就是说在测试类的main方法中,调用子线程的join方法,会阻塞main()方法的执行,当子线程执行完成后,main方法会继续执行,启用第二个子线程会重复以上逻辑。