文章目录
1. 为什么要使用线程池?
如果放任线程无限制地创建,会耗尽CPU资源,并且降低系统的响应速度。
为了更好的对线程进行管理,实现线程的统一分配,调优和监控,降低资源消耗。
JUC设计了线程池。线程池从机制上分为两种.,一是ThreadPoolExecutor,另一类是ForkJoinPool。
- ThreadPoolExecutor
使用多个线程和阻塞队列实现对线程和任务进行管理的工具。 - ForkJoinPool
将一个大任务拆分为很多小任务来异步执行的管理的工具。
1.1 Executor 和 ExecutorService
ThreadPoolExecutor 和 ForkJoinPool 都实现了Executor 和 ExecutorService接口。
Executor 和 ExecutorService接口规定了对线程池使用的提交任务和关闭任务的方法。
1.2 线程池提交任务
execute()
方法是Executor接口规定的。只能接受Runnable类型的任务。
void execute(Runnable command);
submit()
方法是ExecutorService接口规定的。有返回值,且可以通过Future.get()抛出Exception
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
1.3 线程池关闭
-
shutdown()方法,shutdown只是将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断。
-
shutdownNow(),而shutdownNow则是将线程池的状态设置为STOP,正在执行的任务则被停止,没被执行任务的则返回。
shutdownNow会停止正在运行的线程。
2. ThreadPoolExecutor
2.1 构造方法与参数
int corePoolSize
:核心线程数,常驻在线程池内。int maximumPoolSize
:最大线程数。long keepAliveTime
:当线程池里线程数量大于corePoolSize时,会将超时空闲的线程进行关闭,keepAliveTime代表时间计量。TimeUnit unit
:当线程池里线程数量大于corePoolSize时,会将超时空闲的线程进行关闭,unit代表时间单位。BlockingQueue workQueue
:存储线程任务的队列。ThreadFactory threadFactory
:创建线程的方法,可以自定义。RejectedExecutionHandlerhandler
:当线程数大于maximumPoolSize,且workQueue放不下时,拒绝任务的策略,可以自定义。- RejectedExecutionHandlerhandler
构造方法:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
2.2 ThreadPoolExecutor原理
- 线程池首先当前运行的线程数量是否少于corePoolSize。如果是,则创建一个新的工作线程来执行任务。如果都在执行任务,否则进入下一步。
- 判断BlockingQueue是否已经满了,倘若还没有满,则将线程放入BlockingQueue。否则进入下一步
- 如果创建一个新的工作线程将使当前运行的线程数量超过maximumPoolSize,则交给RejectedExecutionHandler来处理任务。
2.3 自定义ThreadPoolExecutor
尽管Executors实现了多个静态方法来创建各种线程池,在正式工作中,我们应当实现自定义的ThreadPoolExecutor 。根据实际场景需求,指定活动线程的数量,限制线程池的大小、创建我们自己的 RejectedExecutionHandler 实现来处理不能适应工作队列的工作。
以下程序创建的线程池,初始大小为2,最大为5,不设置超时,任务队列大小为3,在创建线程时,为线程自定义命名,当超出了线程池处理容量时,将拒绝任务,并将消息写入队列。
public class ThreadFactoryTest {
static AtomicLong cnt = new AtomicLong();
public static void main(String[] args) {
RejectedExecutionHandler handler = new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("我拒绝了任务,并将任务移到MQ中等待以后执行");
}
};
ThreadFactory factory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"线程"+cnt.incrementAndGet());
}
};
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 5, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), factory, handler);
for(int i=0;i<1000;i++) {
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" ,提交任务");
}
});
}
threadPoolExecutor.shutdown();
}
}
3. Executors提供ThreadPoolExecutor构造方法
Executors是一个JUC工具类,实现了多个静态方法来创建各种线程池,下面来了解一下。
在高并发环境中并不建议直接使用Executors的方法,建议实现自定义的ThreadPoolExecutor。
3.1 SingleThreadExecutor
线程池中只有一个线程,因此可以保证所提交任务的顺序执行。
- 核心线程数 1
- 最大线程数 1
- 超时时间为0,即不存在超时移除的情况
- 使用无界的阻塞队列。可存放的任务数为Integer.MAX_VALUE。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
3.2 CachedThreadPool
线程池线程不设上限,使用SynchronousQueue作为阻塞队列,当任务来后立即调度线程执行,如果不够,就创建新线程执行。适用于任务压力不平稳的场景。
- 核心线程数 0
- 最大线程数 Integer.MAX_VALUE
- 超时时间为1分钟,1分钟没有任务的线程会被销毁
- 使用SynchronousQueue作为阻塞队列。可以参考后面介绍JUC集合的博客文章
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
3.3 FixedThreadPool
指定线程池的线程数量,不会主动销毁线程,也不会增加线程数。适用于任务压力较平稳的场景。
线程池的数量一般根据CPU核数、以及CPU的利用率综合考虑,实际工作中通过压测确定。
- 核心线程数 nThreads
- 最大线程数 nThreads
- 超时时间为0,即不存在超时移除的情况
- 使用无界的阻塞队列。可存放的任务数为Integer.MAX_VALUE。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
3.4 ScheduledThreadPool
任务提供延迟或周期执行。核心线程数由程序指定,线程数不限,使用DelayedWorkQueue来存储任务。
- 核心线程数 corePoolSize
- 最大线程数 Integer.MAX_VALUE
- 超时时间为0,即不存在超时移除的情况
- 使用DelayedWorkQueue,为存储周期或延迟任务专门定义的一个延迟队列。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ThreadPoolExecutor(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
总结
为了更好的对线程进行管理,实现线程的统一分配,调优和监控,降低资源消耗,JUC设计了线程池。
Executors实现了多个静态方法来创建各种线程池,在正式工作中,我们应当实现自定义的ThreadPoolExecutor 。
多线程系列在github上有一个开源项目,主要是本系列博客的实验代码。
https://github.com/forestnlp/concurrentlab
如果您对软件开发、机器学习、深度学习有兴趣请关注本博客,将持续推出Java、软件架构、深度学习相关专栏。
您的支持是对我最大的鼓励。