Java线程池的原理

线程池是一种池化技术,用于创建和管理线程,避免频繁的创建和销毁,提高性能和响应速度。

Java线程池有几个核心参数

1、核心线程数

2、最大线程数

3、工作队列

4、最大空闲时间

5、拒绝策略

主要工作原理如下

1、默认情况下是不会创建线程的,只有当任务提交也就是执行了submit方法之后才会创建线程

2、当核心线程数满了之后,也不会新建线程,而是把任务堆积在工作队列中

3、当核心线程满了,并且工作队列也满了才会创建新线程

4、当核心线程满了,并且超过最大线程数的时候就会采取拒绝策略

5、当线程运行一段时间,存在空闲线程的时候,如果空闲线程超过最大空闲时间就会销毁线程,直到线程数等于核心线程数才停止销毁。

通常会使用自定义线程池,一般通过java.util.concurrent.ThreadPoolExecutor构造函数

1
2
3
4
5
6
7
8
9
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 非核心线程空闲存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 任务队列
ThreadFactory threadFactory, // 线程工厂(可选)
RejectedExecutionHandler handler // 拒绝策略
)

Java并发库中的线程池有哪些?他们有什么区别

Java并发库中提供了几种常见的线程池,主要是用Excutor工具类来创建的。

1、FixedThreadPool 创建一个固定线程数的线程池

线程池中的线程数是固定的,如果有空闲线程会被复用,如果线程都在忙,就会放到工作队列中。

这个适合负载稳定的场景,任务量确定不需要动态创建线程的场景。

2、CachedThreadPool 一个根据需要动态创建线程的线程池

这个线程池没有线程数量的上限, 空闲线程60秒会被回收,如果有新任务且没有可用的线程会直接创建新线程

适用短时间内大量并发请求的环境,线程变化很大的环境

3、SingleThreadExcutor 创建只有一个线程的线程池

只有一个线程处理任务,按照任务提交的顺序执行

适合需要保证任务顺序执行,并且不需要并发需求的场景

4、ScheduledThreadPool 支持定时任务和周期性任务的线程池

可以定时或者以固定频率执行任务,线程池大小可以由用户自己定义

适合需要周期性执行任务的场景,如定时任务调度器

5、WorkStealingPool 基于任务窃取算法的线程池

每个线程都会维护一个双端队列deque,线程可以从自己的队列中取任务执行,如果线程任务都执行完,可以从其他线程中的队列中 窃取任务执行,达到负载均衡的效果

适合大量小任务并发执行,特别是递归算法或者大任务分解成小任务的场景。

Java线程池有哪些拒绝策略

一共提供了4种

1、AbortPolicy 当任务队列满且没有线程空闲,此时添加任务会直接抛出异常,这也是默认的拒绝策略。适用于必须通知调用者任务未能被执行的场景。

2、CallerRunsPolicy 当任务队列满且没有线程空闲,此时添加任务由调用者线程执行,适用于希望减缓任务提交速度来稳定系统的场景。

3、DiscartOldestPolicy 当任务队列满且没有空闲线程,会删除最早的任务,然后重新提交当前任务,适用于希望丢弃最旧的任务以保证新的重要人物能够被处理的场景。

4、DiscardPolicy ,直接丢弃当前提交的任务,不会执行任何操作,也不会抛出异常。适用于对部分人物丢弃没有影响的场景,或系统负载较高时不需要处理所有任务的场景。

可以实现RejectedExecutionHandler 接口来定义 自定义的拒绝策略,例如记录日志或者任务重新排队。

为什么线程池要使用阻塞队列,而不是直接增加线程

因为没创建一个线程都会占用一定的系统资源,如栈空间,线程调度开销等,直接增加线程会迅速消耗系统资源,导致性能下降。

使用阻塞队列可以将任务暂存,避免线程数量无限增长,确保资源利用率更高。

如果阻塞队列满了,说明此时系统负载很大,再去增加线程到最大线程数去消化任务即可。