1. 线程池原理
-
优点
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
-
ThreadPoolExecutor的核心参数
- corePoolSize:线程池核心线程数量
- maximumPoolSize:线程池最大线程数量
- keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
- unit:存活时间的单位
- workQueue:存放任务的队列
- handler:超出线程范围和队列容量的任务的处理程序
-
线程处理流程
- 当线程池中线程数量小于 corePoolSize 则创建线程,并处理请求。
- 当线程池中线程数量大于等于 corePoolSize 时,则把请求放入 workQueue 中,随着线程池中的核心线程们不断执行任务,只要线程池中有空闲的核心线程,线程池就从 workQueue 中取任务并处理。
- 当 workQueue 已存满,放不下新任务时则新建非核心线程入池,并处理请求直到线程数目达到 maximumPoolSize(最大线程数量设置值)。
- 如果线程池中线程数大于 maximumPoolSize 则使用 RejectedExecutionHandler 来进行任务拒绝处理。
ThreadPoolExecutor的execute()方法
addIfUnderCorePoolSize(command)
-
饱和策略RejetedExecutionHandler
- AbortPolicy:直接抛出异常
- CallerRunsPolicy:只用调用所在的线程运行任务
- DiscardOldestPolicy:抛弃线程池中工作队列头部的任务(即等待时间最久的任务), 并执行新传入的任务。
- DiscardPolicy:不处理,丢弃掉。
-
线程池类型
-
SingleThreadExecutor:单线程线程池
里面的核心线程数和线程数都是1,并且工作队列使用的是无界队列。由于是单线程工作,每次只能处理一个任务,所以后面所有的任务都被阻塞在工作队列中,只能一个个任务执行。
-
FixedThreadExecutor:固定大小线程池
LinkedBlockingQuene,这个队列是无界的,若消费不过来,会导致内存被任务队列占满,最终oom
-
CachedThreadPool:无界线程池
无界线程池意味着没有工作队列,任务进来就执行,线程数量不够就创建,与前面两个的区别是:空闲的线程会被回收掉,空闲的时间是60s。这个适用于执行很多短期异步的小程序或者负载较轻的服务器。
初始化一个可以缓存线程的线程池,默认缓存60s,线程池的线程数可达到Integer.MAX_VALUE, 即2147483647,内部使用SynchronousQueue作为阻塞队列;和newFixedThreadPool创建的线程池不同,newCachedThreadPool在没有任务执行时,当线程的空闲时间超过keepAliveTime, 会自动释放线程资源,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定 的系统开销,因为线程池的最大值了Integer.MAX_VALUE,会导致无限创建线程;所以,使用该线程池时,一定要注意控制并发的任务数,否则创建大量的线程会导致严重的性能问题。
-
newScheduledThreadPool
创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
-
-
创建线程的几种方法
- ThreadPoolExecutor
- ThreadScheduledExecutor
- ForkJoinPool
-
线程池大小设置
-
CPU密集型
-
尽量使用较小的线程池,一般Cpu核心数+1
因为CPU密集型任务CPU的使用率很高,若开过多的线程,只能增加线程上下文的切换次数,带来额外的开销
-
-
IO密集型
-
可以使用较大的线程池,一般CPU核心数 * 2
IO密集型CPU使用率不高,可以让CPU等待IO的时候处理别的任务,充分利用cpu时间
-
线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
下面举个例子: 比如平均每个线程CPU运行时间为0.5s,而线程等待时间(非CPU运行时间,比如IO)为1.5s,CPU核 心数为8,那么根据上面这个公式估算得到:((0.5+1.5)/0.5)*8=32。这个公式进一步转化为:
-
最佳线程数目=(线程等待时间与线程CPU时间之比+ 1)CPU数目
-
混合型
- 可以将任务分为CPU密集型和IO密集型,然后分别使用不同的线程池去处理,按情况而定
-
-
风险
- 死锁
- 资源不足
- 并发错误
- 线程泄漏
- 请求过载
2. Callable、Future、FutureTask
- Cabllable
- Future
- future.get方法会阻塞当前线程, 直到任务执行完成返回结果为止。
- FutureTask
- 可取消的异步计算,此类提供了对Future的基本实现,仅在计算完成时才能获取结果,如果计算尚未完 成,则阻塞get方法。
- FutureTask不仅实现了Future接口,还实现了Runnable接口,所以不仅可以将FutureTask当成一个 任务交给Executor来执行,还可以通过Thread来创建一个线程。
- 三者之间的关系:
- Callable是Runnable封装的异步运算任务。
- Future用来保存Callable异步运算的结果
- FutureTask封装Future的实体类
- Callable与Runnbale的区别
- Callable定义的方法是call,而Runnable定义的方法是run。
- call方法有