一、为何要使用线程池
在Java中,要使用多线程,除了使用new Thread()之外,还可以使用线程池ExecutorService。
// 使用Thread
Thread t = new Thread(new Runnable() {
@Override
public void run() {
// ...
}
});
t.start();
// 使用线程池
ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(new Runnable() {
@Override
public void run() {
// ...
}
});
线程池主要解决了两个问题:
- 频繁创建销毁线程的开销
- 任务的管理
在异步任务比较多时,创建、销毁线程会占用很多系统资源;这时候,使用线程池,就可以实现线程的复用,让人专注于任务的实现,而不是管理线程。
二、线程池简介
1. 什么是线程池
线程池(本文特指ThreadPoolExecutor类)顾名思义,就是一个装了线程的池子。线程池创建和管理若干线程,在需要使用的时候可以直接从线程池中取出来使用,在任务结束之后闲置等待复用,或者销毁。
线程池中的线程分为两种:核心线程和普通线程。核心线程即线程池中长期存活的线程,即使闲置下来也不会被销毁,需要使用的时候可以直接拿来用。而普通线程则有一定的寿命,如果闲置时间超过寿命,则这个线程就会被销毁。
查看ThreadPoolExecutor类的其中一个典型的构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
线程池的具体行为和几个参数有关:
- 核心数 corePoolSize
线程池中核心线程的数量。 - 最大容量 maximumPoolSize
线程池最大允许保留多少线程。 - 超时时间 keepAliveTime
线程池中普通线程的存活时间。
2. 线程池的使用
线程池的一般使用步骤如下:
使用Executors中的工厂方法来获取ExecutorService实例;
使用ExecutorService的execute(runnable)或者submit(runnable)方法来添加任务。
ExecutorService es = Executors.newSingleThreadExecutor();
es.execute(new Runnable() {
@Override
public void run() {
String response = new HttpUtil().get("http://littlefogcat.top");
System.out.println(response);
}
});
3. 线程池的分类
在Executors工厂类中提供了多种线程池,典型的有以下四种:
1. SingleThreadExecutor 单线程线程池
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
核心线程数为1,最大线程数为1,也就是说SingleThreadExecutor这个线程池中的线程数固定为1。使用场景:当多个任务都需要访问同一个资源的时候。
2. FixedThreadPool 固定容量线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
核心线程数为n,最大线程数为n。使用场景:明确同时执行任务数量时。
3. CachedThreadPool 缓存线程池
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
核心线程数为0,最大线程数无上限,线程超时时间60秒。使用场景:处理大量耗时较短的任务。
4. ScheduledThreadPool 定时线程池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
/*
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
*/
核心线程数自定,最大线程数无上限。使用场景:处理延时任务。
可以看到,这四个方法都返回了一个ThreadPoolExecutor对象(ScheduledThreadPoolExecutor是其子类),仅仅是其中的参数略有不同。所以接下来就对ThreadPoolExecutor类进行解析。
4. ExecutorService.submit()与Executor.execute() 两个方法的相同与不同之处
相同之处:
Both submit() and execute() methods are used to submit a task to Executor framework for asynchronous execution.
submit和execute方法均可以想线程池中提交一个任务,让线程池来异步执行这个任务
Both submit() and execute() can accept a Runnable task.
两个方法均可以接受Runnable类型的任务
You can access submit() and execute() from the ExecutorService interface because it also extends the Executor interface which declares the execute() method.
从ExecutorService接口中均可以调用submit和execute方法,但是submit方法是在ExecutorService接口中定义的,而execute方法是在Executor接口中定义的
不同之处:
The submit() can accept both Runnable and Callable task but execute() can only accept the Runnable task.
submit方法既能接受有返回结果Callable类型和没有返回结果的Runnable类型,而execute方法只能结构没有返回结果的Runnable类型
The submit() method is declared in ExecutorService interface while execute() method is declared in the Executor interface.
submit方法是定义在ExecutorService接口中的,而execute方法是定义在Executor接口中的
The return type of submit() method is a Future object but return type of execute() method is void.
submit方法的返回值是一个Future,而execute方法的返回值是void
对于异常的处理
使用submit方式提交的任务若在执行的过程中抛出了异常的话,异常信息会被吃掉(在控制台中看不到),需要通过Future.get方法来获取这个异常;使用execute方式提交的任务若在执行的过程中出现异常的话,异常信息会被打印到控制台
参考:
https://zhuanlan.zhihu.com/p/143484740
https://www.imangodoc.com/178483.html