线程池

优点
重用线程,减少创建和销毁线程的开销(时间,资源)
防止线程相互竞争资源
线程池类型
-
newFixedThreadPool,coreSize和maxSize大小一致,keepAlived=0永不超时,队列为LinkedBlockingQueue无限制阻塞队列 -
newCachedThreadPool,maxSize=Integer.MAX_VALUE,keepAlived=60,队列为SynchronousQueue -
newSingleThreadPool,coreSize=maxSize=1,keepAlived=0永不超时,队列为LinkedBlockingQueue无限制阻塞队列 newScheduledThreadPool
自定义线程池
通过继承ThreadPoolExecutor来实现自定义线程池
其中核心在于定义几个关键的构造函数参数
public ThreadPoolExecutor(int corePoolSize, // 一直存活的线程数
int maximumPoolSize, // 最多可以同时运行的线程数
long keepAliveTime, // 大于core的线程空闲后,maxCoreSize存活数量的时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 存储等待的线程数量队列(大于coreSize的都存储至此)
ThreadFactory threadFactory,
RejectedExecutionHandler handler) // 线程池拒绝时的动作
- 如果当前线程池线程数目
小于corePoolSize(核心池还没满呢),那么就创建一个新线程去处理任务。 - 如果
corePoolSize已经满了,来了一个新的任务后,会尝试将其添加到任务队列中,如果成功,则等待空闲线程将其从队列中取出并且执行,如果队列已经满了,则继续下一步。
3.此时,如果线程池线程数量小于maximumPoolSize,则创建一个新线程执行该任务(不是从阻塞队列中出一个任务执行,因此不是FIFO),否则,那就说明线程池到了最大饱和能力了,没办法再处理了,此时就按照拒绝策略来处理。(就是构造函数当中的Handler对象)。 - 如果线程池的线程数量大于corePoolSize,则当某个线程的空闲时间
超过了keepAliveTime,那么这个线程就要被kill了,直到线程池中线程数量不大于corePoolSize为止。
阻塞队列的选择
LinkedBlockingQueue: 无限阻塞队列
ArrayBlockingQueue: 有限阻塞队列
PriorityBlockingQueue: 有限带权阻塞队列
SynchronousQueue:
饱和策略
Abort(中止):抛出RejectedExecutionException异常
Discard(丢弃):放弃任务
DiscardOldest(丢弃最旧):放弃最老的任务,然后重新提交任务
CallerRuns(调用者运行):将任务推回调用者,来减缓新任务流
平缓劣化
当线程池满,主线程不调用accept,会阻塞在TCP层,转嫁到用户头上
配置
ThreadPoolExecutor允许通过setter修改配置项,可以使用unconfigurableExecutorService来设置一个代理,屏蔽对线程池参数的修改
切面
定制化的线程池可以使用
beforeExecute(执行前),afterExecute(执行后),terminated(线程池关闭后)来实现切面
阻塞和非阻塞请求
executor.execute(); 这个是非阻塞提交,返回future标识状态
executor.invokeAll(); 这个是阻塞批量提交,全部执行完成,返回批量future标识状态
executor.invokeAny(); 这个是阻塞批量提交,只要有一个执行完成,返回批量future标识状态
submit() -> FutureTask 和 Future
FutureTask是一个阻塞闭锁,实现了Future
get()没有获得值时会阻塞
submit方法实际上是newForFuture();
ThreadFactory
线程池内部的线程是重新定义过的线程对象,因此针对外部线程interrupt,在线程池内部的线程是无法感应的
如果想对内部做干扰,需要自定应ThreadFactory,此工厂可以定义每个生成的threadName,是否精灵线程,线程级别
此对象内部会包装UncaughtExceptionHandle来捕获不可知异常,但是只在execute()生效,submit()会由Future封装
关闭和等待
shutdown() 平滑关闭, 会执行阻塞队列的任务
shutdownNow()强制关闭,使用interrupt,不会执行阻塞队列中的任务,并且返回未执行的任务列表
关闭后提交到ExecutorService中的任务,会被拒绝执行处器理处理,抛出RejectedExecutionException,而这个异常并不是关闭时才抛出的,简单地放弃任务也会抛出此异常
awaitTermination()阻塞当前线程,设置超时时间,等待任务完成
区别:
shutdown后还可以提交任务,awaitTermination后不可以提交任务
自定义线程池
自定义线程池时,如果任务是 CPU 密集型(需要进行大量计算、处理),则应该配置尽量少的线程,比如 CPU 个数 + 1,这样可以避免出现每个线程都需要使用很长时间但是有太多线程争抢资源的情况;
如果任务是IO密集型(主要时间都在 I/O,CPU 空闲时间比较多),则应该配置多一些线程,比如 CPU 数的两倍,这样可以更高地压榨 CPU
为了错误避免创建过多线程导致系统奔溃,建议使用有界队列。因为它在无法添加更多任务时会拒绝任务,这样可以提前预警,避免影响整个系统