搬砖日记-CountDownLatch和CompletableFuture的使用
前言
不知不觉在大厂搬砖快一年了,在这一年里不得不说我学到了很多,特别把之前学到的知识给落地,这给我带来一些满足感和充实感,于是我想着抽空把最近学到的知识给整理整理,既是温习回顾还是一种分享,本篇文章将带领大家了解一下CountDownLatch
和CompletableFuture
的使用.
正文
CountDownLatch
CountDownLatch程序计数器:
- CountDownLatch用于主线程等待其他子线程任务都执行完毕后再执行.
- CountDownLatch是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了.
- 使用的场景:如多模块数据加速加载、治理大批量数据下游接口超时等
CountDownLatch主要的方法有:
- await() :调用
await()
方法的线程会被挂起,它会等待直到count值为0才继续执行 - await(long timeout,TimeUnit unit):只不过等待一定的时间后count值还没变为0的话就会继续执行(适用允许丢失部分数据的场景)
- countDown():将count值减1
事例demo
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class Test020 {
/**
* 线程池
*/
private static final ExecutorService QUERY_POOL = new ThreadPoolExecutor(
10, 10,
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(10000),
new ThreadPoolExecutor.DiscardPolicy());
public static void main(String[] args) {
long start = System.currentTimeMillis();
try {
List<Long> resultList = new ArrayList<>();
CountDownLatch countDownLatch = new CountDownLatch(4);
for (int i = 0; i < 4; i++) {
QUERY_POOL.execute(() -> {
try {
resultList.addAll(dohandler());
} finally {
countDownLatch.countDown();
}
});
}
countDownLatch.await();
System.out.println("结果:" + resultList + "耗时:" + (System.currentTimeMillis() - start));
} catch (Exception e) {
System.out.println("发生异常");
}
}
public static List<Long> dohandler() {
try {
Thread.sleep(2500);
return Lists.newArrayList(123L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
}
运行结果:
CompletableFuture
CompletableFuture
CompletableFuture
是对Future
模式的应用即实现,支持流调用、异步执行,它支持完成后得到通知。CompletableFuture
默认的时候会使用ForkJoinPool
池来执提供线程来执行任务.
ForkJoinPool
线程池在 JDK 8 加入,主要用法和之前的线程池是相同的,也是把任务交给线程池去执行,线程池中也有任务队列来存放任务,和之前的五种线程池不同的是,它非常适合执行可以分解子任务的任务,比如树的遍历,归并排序,或者其他一些递归场景。
CompletableFuture提供了四个静态方法用来创建对象:
- runAsync(Runnable runnable) :同步执行,使用默认线程池
- runAsync(Runnable runnable, Executor executor) :同步执行,手动线程池
- supplyAsync(Supplier supplier) :异步执行,使用默认线程池
- supplyAsync(Supplier supplier, Executor executor):异步执行,手动线程池
事例demo
-
CompletableFuture.get()
会阻塞线程,所以get
操作不要在任务分发循环体内进行,否则整个操作就不是多线程异步操作了.import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;public class Test019 {
public static void main(String[] args) { long start = System.currentTimeMillis(); try { List<Long> resultList = new ArrayList<>(); List<CompletableFuture<List<Long>>> tmp = new ArrayList(); for (int i = 0; i < 4; i++) { final CompletableFuture<List<Long>> resultFuture = CompletableFuture.supplyAsync(() -> { return dohandler(); }); tmp.add(resultFuture); } for (CompletableFuture<List<Long>> tmp2 : tmp) { resultList.addAll(tmp2.get()); } System.out.println("结果:" + resultList + "耗时:" + (System.currentTimeMillis() - start)); } catch (Exception e) { System.out.println("发生异常"); } } public static List<Long> dohandler() { try { Thread.sleep(2500); return Lists.newArrayList(123L); } catch (InterruptedException e) { e.printStackTrace(); } return null; }
}
运行结果:
总结
也许我们可以通过死记硬背的方式学到很多“八股文”,但是往往这样学来的,终究是纸上学来终觉浅,如果能在真实的场景下应用的话,那么才能对此更有体会.