问题思考:
1、什么是线程池? 2、线程池有什么优点? 3、线程池有哪几种创建方式? 4、Executors和ThreaPoolExecutor创建线程池的区别? 5、你知道怎么创建线程池吗?
1. 线程池有什么优点?
Java高并发应用频繁创建和销毁线程的操作是非常低效的,而且是不被编程规范所允许的。如何降低Java线程的创建成本?必须使用到线程池。线程池主要解决了以下两个问题:
(1)提升性能:线程池能独立负责线程的创建、维护和分配。在执行大量异步任务时,可以不需要自己创建线程,而是将任务交给线程池去调度。线程池能尽可能使用空闲的线程去执行异步任务,最大限度地对已经创建的线程进行复用,使得性能提升明显。
(2)线程管理:每个Java线程池会保持一些基本的线程统计信息,例如完成的任务数量、空闲时间等,以便对线程进行有效管理,使得能对所接收到的异步任务进行高效调度。
在主要大厂的编程规范中,不允许在应用中自行显式地创建线程,线程必须通过线程池提供。由于创建和销毁线程需要时间以及系统资源开销,使用线程池的好处是减少这些开销,解决资源不足的问题。
2. 什么是线程池?
在多线程编程中,任务都是一些抽象且离散的工作单元,而线程是使任务异步执行的基本机制。随着应用的扩张,线程和任务管理也变得非常复杂。为了简化这些复杂的线程管理模式,我们需要一个“管理者”来统一管理线程及任务分配,这就是线程池。
在JUC中有关线程池的类与接口的架构图大致如图:
1. Executor
Executor 是Java异步目标任务的“执行者”接口,其目标是执行目标任务。“执行者”Executor 提供了execute() 方法来执行已提交的Runnable执行目标实例。它只包含一个函数式方法:
public interface Executor {
void execute(Runnable command);
}
2. ExecutorService
ExecutorService 继承于Executor ,ExecutorService 提供了“接收异步任务并转交给执行者”的方法,如submit 系列方法、invoke 系列方法等,具体如下:
public interface ExecutorService extends Executor {
<T> Future<T> submit(Callable<T> task);
Future<?> submit(Runnable task);
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
}
3. AbstractExecutorService
AbstractExecutorService 是一个抽象类,它实现了ExecutorService 接口。AbstractExecutorService 存在的目的是为ExecutorService 中的接口提供默认实现。
4. ThreadPoolExecutor
ThreadPoolExecutor 就是大名鼎鼎的“线程池”实现类,它继承于AbstractExecutorService 抽象类。ThreadPoolExecutor 是JUC线程池的核心实现类。线程的创建和终止需要很大的开销,线程池中预先提供了指定数量的可重用线程,所以使用线程池会节省系统资源,并且每个线程池都维护了一些基础的数据统计,方便线程的管理和监控。
5. ScheduledExecutorService
ScheduledExecutorService 是一个接口,它继承于ExecutorService 。它是一个可以完成“延时”和“周期性”任务的调度线程池接口,其功能和Timer/TimerTask 类似。
6. ScheduledThreadPoolExecutor
ScheduledThreadPoolExecutor 继承于ThreadPoolExecutor ,它提供了ScheduledExecutorService 线程池接口中“延时执行”和“周期执行”等抽象调度方法的具体实现。ScheduledThreadPoolExecutor 类似于Timer ,但是在高并发程序中,ScheduledThreadPoolExecutor 的性能要优于Timer 。
7. Executors
Executors是一个静态工厂类,它通过静态工厂方法返回ExecutorService、ScheduledExecutorService 等线程池示例对象,这些静态工厂方法可以理解为一些快捷的创建线程池的方法。
3. Executors的4种快捷创建线程池的方法
Java通过Executors工厂类提供了4种快捷创建线程池的方法:
public class Executors {
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
}
Executors静态工厂类中的方法名 | 功能简介 |
---|
newSingleThreadExecutor () | 创建只有一个线程的线程池 | newFixedThreadPool (nThreads) | 创建固定大小的线程池 | newCachedThreadPool() | 创建一个不限制线程数量的线程池,任何提交的任务都将立即执行,但是空闲线程会得到及时回收 | newScheduledThreadPool() | 创建一个可定期或者延时执行任务的线程池 |
以上为Executors 中4个主要的快捷创建线程池的方法。为何JUC要提供工厂方法呢?原因是使用ThreadPoolExecutor ScheduledThreadPoolExecutor构造器创建普通线程池、可调度线程池比较复杂,这些构造器会涉及大量的复杂参数。尽管Executors 的工厂方法使用方便,但是在生产场景中被很多企业(尤其是大厂)的开发规范所禁用。
4. 线程池的标准创建方式
大部分企业的开发规范都会禁止使用快捷线程池,要求通过标准构造器ThreadPoolExecutor去构造工作线程池。Executors工厂类中创建线程池的快捷工厂方法实际上是调用ThreadPoolExecutor(定时任务使用ScheduledThreadPoolExecutor )线程池的构造方法完成的。
ThreadPoolExecutor构造方法有多个重载版本,其中一个比较重要的构造器如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
很无奈,构造一个线程池竟然有7个参数,但是确实需要这么多参数。
1. 核心和最大线程数量
参数corePoolSize用于设置核心线程池数量,参数maximumPoolSize用于设置最大线程数量。线程池执行器将会根据corePoolSize和maximumPoolSize自动维护线程池中的工作线程,大致规则为:
(1)当在线程池接收到新任务,并且当前工作线程数少于corePoolSize时,即使其他工作线程处于空闲状态,也会创建一个新线程来处理该请求,直到线程数达到corePoolSize。
(2)如果当前工作线程数多于corePoolSize数量,但小于maximumPoolSize数量,那么仅当任务排队队列已满时才会创建新线程。通过设置corePoolSize和maximumPoolSize相同,可以创建一个固定大小的线程池。
(3)当maximumPoolSize被设置为无界值(如Integer.MAX_VALUE )时,线程池可以接收任意数量的并发任务。
(4)corePoolSize和maximumPoolSize不仅能在线程池构造时设置,也可以调用setCorePoolSize()和setMaximumPoolSize() 两个方法进行动态更改。
2. BlockingQueue
BlockingQueue(阻塞队列)的实例用于暂存接收到的异步任务,如果线程池的核心线程都在忙,那么所接收到的目标任务缓存在阻塞队列中。
3. keepAliveTime
线程构造器的keepAliveTime(空闲线程存活时间)参数用于设置池内线程最大Idle(空闲)时长,如果超过这个时间,默认情况下Idle 、非Core线程会被回收。
如果池在使用过程中提交任务的频率变高,也可以调用方法setKeepAliveTime(long,TimeUnit) 进行线程存活时间的动态调整,可以将时长延长。如果需要防止Idle线程被终止,可以将Idle时间设置为无限大,具体如下:
setKeepAliveTime(Long.MAX_VALUE,TimeUnit.NANOSECONDS);
默认情况下,Idle超时策略仅适用于存在超过corePoolSize线程的情况。但若调用了allowCoreThreadTimeOut(boolean) 方法,并且传入了参数true,则keepAliveTime参数所设置的Idle超时策略也将被应用于核心线程。
|