← 返回首页
Java控制线程执行顺序的三种方式
发表时间:2022-11-29 22:52:01
Java控制线程执行顺序的三种方式

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的可见性实现。