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。
当我们调用 CountDownLatch
的 countDown()
方法时,N 就会减 1,CountDownLatch
的 await
方法会阻塞当前线程,直到 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");
}
}