线程池
《阿里巴巴 Java 手册》中说到,可见线程池的重要性
Executor
Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。
主要有三种 Executor:
- CachedThreadPool:直接提交,一个任务创建一个线程;
- FixedThreadPool:所有任务只能使用固定大小的线程;
- SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool。
- ScheduledThreadPool: 创建一个定长线程池,支持定时及周期性任务执行。用到了DelayedWorkQueue
但是本质上,三种executor都是通过new ThreadPoolExecutor实现的
建议自己用ThreadPoolExecutor自己实例化出来
ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
corePoolSize:核心池的大小,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止
unit:参数keepAliveTime的时间单位
workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:
- ArrayBlockingQueue:基于数组的FIFO队列,是有界的,创建时必须指定大小
- LinkedBlockingQueue: 基于链表的FIFO队列,是无界的,默认大小是
Integer.MAX_VALUE
- SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现<线程数达到了
maximumPoolSize
而不能新建线程>的错误,使用这个类型队列的时候,maximumPoolSize
一般指定成Integer.MAX_VALUE
,即无限大
rejectedExecutionHandler:任务拒绝处理器 一般用AbortPolicy
- AbortPolicy 丢弃任务,抛运行时异常
- CallerRunsPolicy 执行任务
- DiscardPolicy 忽视,什么都不会发生
- DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务
两种情况会拒绝处理任务:
- 当线程数已经达到maxPoolSize,切队列已满,会拒绝新任务
- 当线程池被调用shutdown()后,会等待线程池里的任务执行完毕,再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务
实现RejectedExecutionHandler接口,可自定义处理器
执行线程:
通常我们都是使用:
threadPool.execute(new Job());
这样的方式来提交一个任务到线程池中,所以核心的逻辑就是 execute()
函数了。
在具体分析之前先了解下线程池中所定义的状态,这些状态都和线程的执行密切相关:
用图表示 :
RUNNING
自然是运行状态,指可以接受任务执行队列里的任务SHUTDOWN
指调用了shutdown()
方法,不再接受新任务了,但是队列里的任务得执行完毕。STOP
指调用了shutdownNow()
方法,不再接受新任务,同时抛弃阻塞队列里的所有任务并中断所有正在执行任务。TIDYING
所有任务都执行完毕,在调用shutdown()/shutdownNow()
中都会尝试更新为这个状态。TERMINATED
终止状态,当执行terminated()
后会更新为这个状态。
查看excute()源码:
如下图所示:
- 获取当前线程池的状态。
- 当前线程数量小于 coreSize 时创建一个新的线程运行。
- 如果当前线程处于运行状态,并且写入阻塞队列成功。
- 双重检查,再次获取线程状态;如果线程状态变了(非运行状态)就需要从阻塞队列移除任务,并尝试判断线程是否全部执行完毕。同时执行拒绝策略。
- 如果当前线程池为空就新创建一个线程并执行。
- 如果在第三步的判断为非运行状态,尝试新建线程,如果失败则执行拒绝策略
当线程池中线程的数目大于corePoolSize时,便将任务放入任务缓存队列里面,当任务缓存队列满了之后,便创建新的线程(最多不超过maximumPoolSize,所以说 maximumPoolSize更像是补救措施),所以如果是无界队列,maximumPoolSize没有任何意义