并发工具类之 CountDownLatch

x33g5p2x  于2021-12-18 转载在 其他  
字(3.1k)|赞(0)|评价(0)|浏览(341)

CountDownLatch 允许一个或多个线程等待其他线程完成操作。

假如我们有这样一个需求:解析一个 EXCEL 里多个 sheet 的数据,此时可以考虑使用多线程, 每一个线程解析一个 sheet 里的数据,等到所有的 sheet 都解析完之后,程序提示解析完成。对于这个需求,最简单的做法就是使用 join(),代码如下:

public class TestJoin {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                System.out.println("t1 start");
                TimeUnit.SECONDS.sleep(3);
                System.out.println("t1 finish");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                System.out.println("t2 start");
                TimeUnit.SECONDS.sleep(3);
                System.out.println("t2 finish");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t3 = new Thread(() -> {
            try {
                System.out.println("t3 start");
                TimeUnit.SECONDS.sleep(3);
                System.out.println("t3 finish");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        t2.start();
        t3.start();
        try {
            t1.join();
            t2.join();
            t3.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("所有 sheet 解析完毕");
    }
}

控制台输出如下(每次运行结果可能不同):

t1 start
t2 start
t3 start
t2 finish
t3 finish
t1 finish
所有 sheet 解析完毕

join 用于让当前执行线程等待 join 线程执行结束,其原理是不停地检查 join 线程是否存活,如果 join 线程存活则让当前线程等待,其实现原理如下代码:

// 如果 join 线程一直存活则一直循环
while (isAlive()) {
    // wait(0) 表示一直等待下去
    wait(0);
}

join 线程终止后,线程的 this.notifyAll() 方法会被调用,调用 notifyAll() 方法是在 JVM 里实现的,感兴趣的小伙伴可以参考下面这篇文章:【Java】Thread类中的join()方法原理

除了使用 join() 方法外,我们还可以使用 CountDownLatch 来实现上述需求,代码如下:

public class TestCountDownLatch {
    public static void main(String[] args) {
        CountDownLatch c = new CountDownLatch(3);
        Thread t1 = new Thread(() -> {
            try {
                System.out.println("t1 start");
                TimeUnit.SECONDS.sleep(5);
                System.out.println("t1 finish");
                c.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                System.out.println("t2 start");
                TimeUnit.SECONDS.sleep(3);
                System.out.println("t2 finish");
                c.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t3 = new Thread(() -> {
            try {
                System.out.println("t3 start");
                TimeUnit.SECONDS.sleep(3);
                System.out.println("t3 finish");
                c.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        t2.start();
        t3.start();
        try {
            c.await();
            System.out.println("所有 sheet 解析完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

控制台输出如下(每次运行输出可能不同):

t1 start
t3 start
t2 start
t3 finish
t2 finish
t1 finish
所有 sheet 解析完毕

CountDownLatch 的构造器接收一个 int 类型的参数作为计数器,如果你想等待 N 个点完成,这里就传入 N。

当我们调用 CountDownLatchcountDown() 方法时,N 就会减 1,CountDownLatchawait 方法会阻塞当前线程,直到 N 变成 0。但有时候某个 sheet 解析的可能特别慢,我们不能一直让主线程等着,我们可以调用另一个方法 await(long timeout, TimeUnit unit),这个方法在等待特定时间后,就不会再阻塞当前线程了,join 也有类似的方法。

下面是一个测试案例,感兴趣的小伙伴可以看一下,代码如下:

public class T06_CountDownLatch {
    public static void main(String[] args) {
        Thread[] threads = new Thread[100];
        // 计一个数 latch 100
        CountDownLatch latch = new CountDownLatch(threads.length);
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                int result = 0;
                for (int j = 0; j < 1000000000; j++) {
                    result += j;
                }
                // 当前线程结束后 latch 减 1
                latch.countDown();
            });
        }
        for (Thread thread : threads) {
            thread.start();
            // 如果在这里使用 join,也可以实现同样的效果
            // thread.join();
        }
        try {
            // 等待 latch 等于 0 后才继续执行之后的代码
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("latch end");
    }
}

相关文章