主线程等待

场景介绍

在实际的工作过程中,为了减少用户的等待时间,通常会使用多线程去并行处理相关任务。主任务线程等待其他并行任务处理完成后,获取执行的结果,经过相关处理,返回给用户。这种方式是多线程在程序中主要的使用方式,因此这就要求主线程必须等待任务线程的执行,然后汇总结果。

实现方法

join方法

在 CountDownLatch 类未出现之前要实现主线程等待只能使用 join 方法(这样说有点绝对,因为 Future 的 get 方法也能实现闭锁的功能,但是需要自己将其缓存后再去循环处理,不是使用 JDK 所提供的方法了)。join 方法和之前介绍的方法不同,它是线程 Thread 类的方法,它没有入参也没有返回值。

示例代码
public static void main(String[] args) throws InterruptedException {
        System.out.println("main start");
        Thread t1 = new Thread(() -> {
            System.out.println("t1 start");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 end");
        });
        Thread t2 = new Thread(() -> {
            System.out.println("t2 start");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t2 end");
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("main end");
    }              

如上代码中,创建了两个子线程 t1 和 t2, 然后分别启动两个子线程并调用各自的 join 方法。方法执行后,会先执行两个子线程,执行完后才会执行主线程的打印任务。
执行过程为,当主线程执行到 t1.join() 的时候其会被阻塞,等待 t1 执行完后再执行 t2.join() 会再次被阻塞,等到 t2 执行完后,再执行主线程的打印任务。
需要注意的是,要先启动后再调用 join 方法才会有用,如果先调用 join 方法再启动是不会生效的。

CountDownLatch

上面我们介绍了通过使用线程类的 join 方法来让主线程等待,但是这个方法不够灵活,对实际工作的各种场景并不能完全满足,因此 JDK 提供了 CountDownLatch 类,可以让我们能更好的实现该功能。

示例代码
public static void main(String[] args) throws InterruptedException {
        System.out.println("main start");
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            try {
                System.out.println("t1 start");
                Thread.sleep(500);
                System.out.println("t1 end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }).start();
        new Thread(() -> {
            try {
                System.out.println("t2 start");
                Thread.sleep(1000);
                System.out.println("t2 end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }).start();
        countDownLatch.await();
        System.out.println("main end");
    }
}

如上代码中,创建了一个 CountDownLatch 对象,由于有两个子线程,所以构造函数传入的参数为2。然后创建了两个子线程,在各自的 finally 模块中调用 CountDownLatch 对象的 countDown 方法;分别启动两个子线程,最后由主线程调用 CountDownLatch 对象的 await 方法,并执行打印任务。
代码的执行过程为,在创建 CountDownLatch 对象时,构造方法传入了计数器数量2,主线程调用 CountDownLatch 对象的 await 方法时将会被阻塞,直到 CountDownLatch 对象中的计数器变为0,主线程才会返回,阻塞也就结束了。在子线程执行时,由于调用了 CountDownLatch 对象的 countDown 方法,每次调用计数器都会减1 ,两次调用后计数器变为0,主线程结束阻塞打印日志。
需要注意的是,CountDownLatch 对象的 await 方法被返回时,只需要 CountDownLatch 对象的计数器变为0即可,所以只需要调用 countDown 方法的次数为计数器的数量即可,并不需要各线程都执行完成,示例代码如下:

public static void main(String[] args) throws InterruptedException {
        System.out.println("main start");
        CountDownLatch countDownLatch = new CountDownLatch(2);
        new Thread(() -> {
            try {
                System.out.println("t1 start");
                Thread.sleep(2000);
                System.out.println("t1 end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
            }
        }).start();
        new Thread(() -> {
            try {
                System.out.println("t2 start");
                Thread.sleep(1000);
                System.out.println("t2 end");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                countDownLatch.countDown();
                countDownLatch.countDown();
            }
        }).start();
        countDownLatch.await();
        System.out.println("main end");
    }

总结

1、join 方法是 Thread 类的方法,调用它时主线程会阻塞到调用它的线程执行完后才执行。
2、CountDownLatch 类是 JDK 专门提供的一个用来操作线程等待的类,它是使用计数器的方法,并不关心各线程是否执行完成,因此它更加的灵活。
3、由于在实际工作中,常常使用线程池来管理程序中的线程,因此使用 CountDownLatch 类来处理会更加灵活实用。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容