为什么需要线程池
- 线程也是对象,频繁的创建销毁,系统开销较大,也会影响垃圾回收。
- 因为系统中的线程数量如果过多,会导致上下文切换,影响效率。
线程池实现原理
- 需要一个容器存储线程(Worker)
- 需要一个阻塞队列,存储不能马上执行的线程
- 类似一个生产者消费者模式,线程容器不断从阻塞队列中取任务消费,主线程不断添加任务到阻塞队列中
- 如果队列为空,则线程消费阻塞(take阻塞),如果队列满,则添加任务阻塞
- 对线程进行包装,线程start之后如果任务不为空或者阻塞队列取不为空,则调用run方法。
while(task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
try {
log.debug("正在执行...{}", task);
task.run();
Java自带线程池
线程池状态
RUNNING
SHUTDOWN 不会接收新任务,但会处理阻塞队列剩余任务
STOP 中断正在执行的任务,抛弃阻塞队列中任务。
TERMINATED 终结
构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize 核心线程数
- maximumPoolSize最大线程数(最大线程数-核心线程数=救急线程数)
- keepAliveTime线程存活时间-针对救急线程
- unit 存活时间单位-针对救急线程
- workQueue 阻塞队列
- threadFactory 线程创建工程,线程命名之类的
- handler 拒绝策略
执行流程
拒绝策略
- AbortPolicy 让调用者抛出 RejectedExecutionException 异常,这是默认策略,抛出异常会让后面的线程全部取消。
- CallerRunsPolicy 让调用者运行任务
- DiscardPolicy 放弃本次任务
- DiscardOldestPolicy 放弃队列中最早的任务,本任务取而代之
- Netty 的实现,是创建一个新线程来执行任务(不推荐)
线程池创建
- newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
线程数量固定,核心线程数=最大线程数,无救急线程。使用LinkedBlockingQueue作为阻塞队列,无边界。
适合任务量已知,并且耗时操作
- newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
核心线程数是0,全都是救急线程
阻塞队列用的是SynchronousQueue,没有容量,需要同时存取
适合任务密级,但是每个任务执行时间都较短
- newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
多个任务排序执行,多的任务会放到无解队列中排序
相比自己创建线程,如果出错,会挂掉,线程池还会创建新线程,不影响后续任务。
任务提交
// 执行任务
void execute(Runnable command);
// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);
此外还有批量提交任务方法。
关闭线程池
//状态变为SHUTDOWN,不再接收新任务,但已经提交的任务会继续执行,阻塞队列的也会执行。
void shutdown();
//状态变为STOP,不接受新任务,中断正在执行的任务,将队列中的任务返回
List<Runnable> shutdownNow();
线程池参数设置
- 如果线程池大小过小,则会导致饥饿。
- 如果线程池大小过大,则会不断进行上下文切换,并且占用内存。
CPU密集型
CUP核心数+1,加1原因是防止某些原因线程不能执行可以由剩下的补上。
IO密集型
线程数=核心数期望的利用率(总时间)/cpu计算时间
不好把控的话,可以先设置为核心数的2倍,后期再优化。
为什么建议使用默认的线程池
固定大小线程池、和单个线程池使用了无界阻塞队列,任务过多会OOM
Cache线程池的大小是无界的,任务过多也会OOM