前言
请大家优先阅读美团技术团队写的Java线程池实现原理及其在美团业务中的实践。https://blog.csdn.net/heijunwei/article/details/112985523,本文仅仅是对这篇文章没有涉及的知识点而且比较重要的补充,狗尾续貂。
executort优先于线程,jdk1.5中已经增加了java.util.concurrent。这个字包中包含了一个ExecutorFramework它是一个很灵活的基于接口的任务执行工具。
线程池性能优于线程:Java语言虽然内置了多线程支持,启动一个新线程非常方便,但是,创建线程需要操作系统资源(线程资源,栈空间等),频繁创建和销毁大量线程需要消耗大量时间,那么我们就可以把很多小任务让一组线程来执行,而不是一个任务对应一个新线程。简单地说,线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待状态。如果有新任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,新任务要么放入队列等待,要么增加一个新线程进行处理。
线程池提供了更丰富易用的功能:,可以等待完成一项特殊的任务,可以等待一个任务集合中的任何任务或者所有任务完成(利用invokeAny或者invokeAll方法),可以等待executorservice优雅地完成终止(利用awaitTermination方法),可以在任务完成时逐个地获取这些任务的结果(利用ExecutorCompletionService),可以调度在某个特殊的时间段定时运行或者阶段性地运行的任务(利用ScheduledThreadPoolExecutor),等等。
一、线程池分类:
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
1.1、newCachedThreadPool
创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。
1.2、newFixedThreadPool
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数nThreads 线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
1.3、newScheduledThreadPool
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。(定时器)
1.4、newSingleThreadExecutor
Executors.newSingleThreadExecutor()返回一个线程池(这个线程池只有一个线程),这个线程池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去!
二、线程池应用实例:
2.1、newFixedThreadPool应用
public class Main {
public static void main(String[] args) {
// 创建一个固定大小的线程池:
ExecutorService es = Executors.newFixedThreadPool(4);
for (int i = 0; i < 6; i++) {
es.submit(new Task("" + i));
}
// 关闭线程池:
es.shutdown();
}
}
class Task implements Runnable {
private final String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("start task " + name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("end task " + name);
}
}
2.2、newScheduledThreadPool应用
ScheduledExecutorService scheduledThreadPool= Executors.newScheduledThreadPool(3);
scheduledThreadPool.schedule(new Runnable(){
@Override
public void run() {
System.out.println("延迟三秒");
}
}, 3, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(new Runnable(){
@Override
public void run() {
System.out.println("延迟1秒后每三秒执行一次");
}
},1,3,TimeUnit.SECONDS);
2.3、Future应用
class Task implements Callable<String> {
public String call() throws Exception {
return longTimeCalculation();
}
}
ExecutorService executor = Executors.newFixedThreadPool(4);
// 定义任务:
Callable<String> task = new Task();
// 提交任务并获得Future:
Future<String> future = executor.submit(task);
// 从Future获取异步执行返回的结果:
String result = future.get(); // 可能阻塞
当我们提交一个Callable
任务后,我们会同时获得一个Future
对象,然后,我们在主线程某个时刻调用Future
对象的get()
方法,就可以获得异步执行的结果。在调用get()
时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么get()
会阻塞,直到任务完成后才返回结果。
一个Future<V>
接口表示一个未来可能会返回的结果,它定义的方法有:
get()
:获取结果(可能会等待)get(long timeout, TimeUnit unit)
:获取结果,但只等待指定的时间;cancel(boolean mayInterruptIfRunning)
:取消当前任务;isDone()
:判断任务是否已完成。
2.4、CompletableFuture应用
public class Main {
public static void main(String[] args) throws Exception {
// 两个CompletableFuture执行异步查询:
CompletableFuture<String> completableFutureLottery = CompletableFuture.supplyAsync(() -> {
return queryLucyUser("多样抽奖", "0");
});
CompletableFuture<String> completableFutureBox = CompletableFuture.supplyAsync(() -> {
return queryLucyUser("宝箱抽奖", "1");
});
// 用anyOf合并为一个新的CompletableFuture:
CompletableFuture<Object> completableFuture = CompletableFuture.anyOf(completableFutureLottery, completableFutureBox);
// 两个CompletableFuture执行异步查询:
CompletableFuture<Double> cfFetchFromLottery = completableFuture.thenApplyAsync((code) -> {
return fetchLotteryPrice((String) code, "hjw_lottery");
});
CompletableFuture<Double> cfFetchFromBox = completableFuture.thenApplyAsync((code) -> {
return fetchLotteryPrice((String) code, "hjw_box");
});
// 用anyOf合并为一个新的CompletableFuture:
CompletableFuture<Object> future = CompletableFuture.anyOf(cfFetchFromLottery, cfFetchFromBox);
// 最终结果:
future.thenAccept((result) -> {
System.out.println("奖项价格: " + result);
});
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
Thread.sleep(200);
}
static String queryLucyUser(String name, String type) {
System.out.println("queryLucyUser " + name + "...");
try {
Thread.sleep((long) (Math.random() * 200));
} catch (InterruptedException e) {
}
return "hjw";
}
static Double fetchLotteryPrice(String code, String name) {
System.out.println("fetchLucyUser " + name + "...");
try {
Thread.sleep((long) (Math.random() * 200));
} catch (InterruptedException e) {
}
return 5 + Math.random() * 20;
}
}
使用Future
获得异步执行结果时,要么调用阻塞方法get()
,要么轮询看isDone()
是否为true
,这两种方法都不是很好,因为主线程也会被迫等待。从Java 8开始引入了CompletableFuture
,它针对Future
做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。