Java的并发编程中我们经常在主线程中开启多个子线程,子线程的执行顺序并不关心,但是必须要求子线程全部结束后,才能继续执行主线程,这里就是涉及到控制主子线程的执行顺序。我们如何控制线程的执行顺序呢?
1.使用join
join是定义在Thread类中的方法,作用是阻塞当前线程的执行,等到被调用join的线程对象执行完毕才执行继续执行当前线程。从join方法的源码来看,join方法的本质调用的是Object中的wait方法实现主线程的阻塞效果。
实例:
public class ThreadJoinDemo {
static class MyRunnable implements Runnable {
public MyRunnable() {
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "=>" + i);
try {
Thread.sleep(200);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Runnable r = new MyRunnable();
Thread th1 = new Thread(r, "ThreadA");
Thread th2 = new Thread(r, "ThreadB");
Thread th3 = new Thread(r, "ThreadC");
System.out.println("startup three subthread...");
th1.start();
th2.start();
th3.start();
System.out.println("waiting for subthread hash ended...");
try {
th1.join();
th2.join();
th3.join();
} catch (Exception ex) {
ex.printStackTrace();
}
System.out.println("the main thread will ending");
}
}
运行结果:
startup three subthread...
waiting for subthread hash ended...
ThreadC=>0
ThreadB=>0
ThreadA=>0
ThreadB=>1
ThreadC=>1
ThreadA=>1
ThreadB=>2
ThreadA=>2
ThreadC=>2
ThreadB=>3
ThreadC=>3
ThreadA=>3
ThreadC=>4
ThreadB=>4
ThreadA=>4
ThreadC=>5
ThreadB=>5
ThreadA=>5
ThreadA=>6
ThreadC=>6
ThreadB=>6
ThreadB=>7
ThreadA=>7
ThreadC=>7
ThreadB=>8
ThreadC=>8
ThreadA=>8
ThreadB=>9
ThreadC=>9
ThreadA=>9
the main thread will ending
2.使用CountDownLatch
如果子线程是通过线程池的方式启动,由于我们无法拿到线程池启动后的线程对象,那么就无法调用线程的join方法来控制线程的执行顺序了。对于线程池方式启动的线程,推荐大家使用Java并发工具包(java.util.concurrent)提供的CountDownLatch工具类实现控制主子线程的执行顺序。
通过使用 CountDownLatch可以使当前线程阻塞,等待其他线程完成给定任务。CountDownLatch的构造函数接收一个int类型的参数作为计数器,如果你想等待N个点完 成,这里就传入N。当我们调用CountDownLatch的countDown()方法时,N就会减1,CountDownLatch的await()方法 会阻塞当前线程,直到N变成零。
实例:
public class CountDownLatchDemo {
static class MyRunnable implements Runnable {
private CountDownLatch countDownLatch;
public MyRunnable(CountDownLatch countDownLatch){
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "=>" + i);
try {
Thread.sleep(200);
} catch (Exception ex) {
ex.printStackTrace();
}
}
countDownLatch.countDown(); //标记一个子线程已经结束
}
}
public static void main(String[] args) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(3);
ExecutorService pool = Executors.newFixedThreadPool(3);
Runnable r = new MyRunnable(countDownLatch);
System.out.println("startup three subthread...");
for(int i=0;i<3;i++){
pool.execute(r);
}
System.out.println("waiting for subthread has ended...");
countDownLatch.await(); //等待三个子线程结束
System.out.println("the main thread will ending");
pool.shutdownNow();
}
}
运行结果:
startup three subthread...
waiting for subthread has ended...
pool-1-thread-2=>0
pool-1-thread-3=>0
pool-1-thread-1=>0
pool-1-thread-1=>1
pool-1-thread-2=>1
pool-1-thread-3=>1
pool-1-thread-3=>2
pool-1-thread-1=>2
pool-1-thread-2=>2
pool-1-thread-2=>3
pool-1-thread-1=>3
pool-1-thread-3=>3
pool-1-thread-2=>4
pool-1-thread-3=>4
pool-1-thread-1=>4
pool-1-thread-1=>5
pool-1-thread-2=>5
pool-1-thread-3=>5
pool-1-thread-2=>6
pool-1-thread-3=>6
pool-1-thread-1=>6
pool-1-thread-2=>7
pool-1-thread-1=>7
pool-1-thread-3=>7
pool-1-thread-2=>8
pool-1-thread-1=>8
pool-1-thread-3=>8
pool-1-thread-3=>9
pool-1-thread-2=>9
pool-1-thread-1=>9
the main thread will ending
3.使用volatile关键字
通过上面两个例子,我们明白了要控制主子线程的执行顺序的关键因素在于:主线程能否判断子线程是否结束?我们可以设计一个布尔类型数组来记录每个子线程的执行状态,false表示未结束,true表示已结束。为了保证线程的可见性,该数组必须是否volatitle关键字来修饰,否则主线程无法获得子线程更新之后的值。
实例:
public class VolatitleDemo {
//注意这里必须使用volatile关键字修饰,保证线程的可见性。
static volatile boolean[] flags = new boolean[3];
static class MyRunnable implements Runnable {
private int index;
public MyRunnable() {
}
public MyRunnable(int index){
this.index = index;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "=>" + i);
try {
Thread.sleep(200);
} catch (Exception ex) {
ex.printStackTrace();
}
}
flags[index] = true; //标志该子线程已经结束
}
}
public static void main(String[] args) {
Runnable r1 = new MyRunnable(0);
Runnable r2 = new MyRunnable(1);
Runnable r3 = new MyRunnable(2);
Thread th1 = new Thread(r1, "ThreadA");
Thread th2 = new Thread(r2, "ThreadB");
Thread th3 = new Thread(r3, "ThreadC");
System.out.println("startup three subthread...");
th1.start();
th2.start();
th3.start();
System.out.println("waiting for subthread has ended...");
//通过死循环判断子线程是否结束
for(int i=0;i<flags.length;){
if(flags[i]){
i++;
}
}
System.out.println("the main thread will ending");
}
}
运行结果:
startup three subhread...
waiting for subthread is ending...
ThreadA=>0
ThreadC=>0
ThreadB=>0
ThreadA=>1
ThreadB=>1
ThreadC=>1
ThreadC=>2
ThreadB=>2
ThreadA=>2
ThreadB=>3
ThreadA=>3
ThreadC=>3
ThreadC=>4
ThreadA=>4
ThreadB=>4
ThreadB=>5
ThreadC=>5
ThreadA=>5
ThreadA=>6
ThreadC=>6
ThreadB=>6
ThreadC=>7
ThreadB=>7
ThreadA=>7
ThreadA=>8
ThreadB=>8
ThreadC=>8
ThreadA=>9
ThreadC=>9
ThreadB=>9
the main thread will ending
小结:
Java并发编程控制主子线程执行顺序我们可以使用join方法阻塞主线程。如果子线程是通过线程池方式启动推荐使用CountDownLatch工具类实现,也可以利用volatitle的可见性实现。