相比于线程池, 我们可能接触 new Thread 更多一点, 既然有了 new Thread 我们为什么还要使用线程池呢?
new Thread 的弊端
a, 每次 new Thread 新建对象, 性能差
b, 线程缺乏统一管理, 可能无限制的新建线程, 相互竞争, 有可能占用过多系统资源导致死机或者 OOM(OutOfMemory)
c, 缺少更多功能, 如更多执行, 定期执行, 线程中断
线程池的优势
a, 重用存在的线程, 减少对象创建, 消亡的开销, 性能好
b, 可有效控制最大并发线程数, 提高系统资源利用率, 同时可以避免过多资源竞争, 避免阻塞
c, 提供定时执行, 定期执行, 单线程, 并发数控制等功能
线程池类图
由类图可以看出, ThreadPoolExecutor 类是线程池中最核心的一个类. 我们来做重点分析
ThreadPoolExecutor 构造参数
a,corePoolSize: 核心线程数量, 默认情况下 (可预创建线程) 线程池后线程池中的线程数为 0, 当有任务后当有任务后就会创建一个线程去执行任务, 当线程池中的线程数目达到 corePoolSize 后, 就会把到达的任务放到缓存队列中
b,maximumPoolSize: 线程最大线程数
c,workQueue: 阻塞队列, 存储等待执行的任务, 有三种取值, ArrayBlockQueue(基于数组的先进先出队列, 创建时必须指定大小),LinkedBlockingQueue(基于链表的先进先出队列, 如果没有指定此队列大小, 默认为 Integer.MAX_VALUE),SynchronousQueue(不会保存提交的任务, 直接新建一个线程来执行新的任务)
d,keepAliveTime: 线程没有任务执行时最多保持多久时间终止, 默认情况只有当线程池中的线程数大于 corePoolSize 时, keepAliveTIme 才会起作用, 当线程池中的线程数大于 corePoolSize, 如果一个线程的空闲时间达到 keepAliveTime, 则会终止. 如果调用 allowCoreThreadTimeOut(boolean)方法, 在线程池中的线程数不大于 corePoolSize 时 keepAliveTime 参数也会起作用, 直到线程池的线程数为 0
e,unit:keepAliveTime 的时间单位, 有 7 中取值, 如: TimeUnit.DAYS; 天, 可具体到纳秒
f,threadFactory: 线程工厂, 用来创建线程
g,rejectHandler: 当拒绝处理任务时的策略, 通常有四种取值, ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出 RejectedExecutionException 异常; ThreadPoolExecutor.DiscardPolicy: 丢弃任务, 但不抛出异常; ThreadPoolExecutor.DiscardOldestPolicy: 丢弃队列最前面的任务, 重新尝试执行任务(重复此过程);ThreadPoolExecutor.CallerRunsPolicy: 由调用线程处理该任务
参数之间的关系如下:
a, 如果当前 poolsize 小于 corePoolSize, 创建新线程执行任务
b, 如果当前 poolsize 大于 corePoolsize, 且等待队列未满, 进入等待队列
c, 如果当前 poolsize 大于 corePoolsize 且小于 maximumPoolSize, 且等待队列已满, 创建新线程执行任务
d, 如果当前 poolsize 大于 corePoolSize 且大于 maximumPoolSize, 且等待队列已满则用拒绝策略来处理该任务
e, 线程池中的线程执行完任务后不会立刻退出, 而是去检查等待队列是否有新的线程去执行, 如果在 keepAliveTime 里等不到新任务, 线程就会退出
ThreadPoolExecutor 状态
图中需要注意的是 shutdown()和 shutdownNow()方法的区别, 执行前者后, 还在执行的线程会执行完再关闭, 执行后者后, 线程池会立刻关闭, 正在执行的线程不再执行
ThreadPoolExecutor 方法
a,execute(): 提交任务, 交给线程池执行
b,submit(): 提交任务, 能够返回执行结果 execute+Future
c,shutdown(): 关闭线程池, 等待任务都执行完
d,shutdownNow(): 关闭线程池, 不等待任务执行完
e,getTaskCount(): 线程池已执行的和未执行的任务总数
f,getCompletedTaskCount(): 已完成的任务数量
g,getPoolSize(): 线程池当前线程数量
h,getActiveCount(): 当前线程池正在执行任务的线程数量
Executors 类
由上面的类图可以看出, Executors 类为 Executor,ExecutorService,ScheduledExecutorService 提供了一些工具方法, 并且 Executors 封装了 ScheduledThreadPoolExecutor 类和 ThreadPoolExecutor 类, 所以我们倡导在实际使用中使用此类, Executors 类提供了四个静态方法:
a,newCachedThreadPool: 创建可缓存线程池, 灵活回收空闲线程, 如果没有可回收线程, 则创建新线程
由上图可看出, newCachedThreadPool 将 corePoolSize 设置为 0, 将 maximumPoolSize 设置为 Integer.MAX_VALUE, 使用 SynchronousQueue, 就是说有新任务就创新线程运行, 线程空闲超过 60 秒, 则销毁线程. demo 代码如下:
- public static void main(String[] args) {
- ExecutorService executorService = Executors.newCachedThreadPool();
- for (int i = 0; i < 10; i++) {
- final int index = i;
- executorService.execute(new Runnable() {
- @Override
- public void run() {
- log.info("task:{}", index);
- }
- });
- }
- executorService.shutdown();
- }
b,newFixedThreadPool: 创建定长线程池, 可控制线程最大并发数, 超出的线程会在队列中等待
newFixedThreadPool 创建的线程池 corePoolSize 和 maximumPoolSize 值相等, 线程空闲后直接销毁, 使用的是 LinkedBlockingQueue.demo 代码如下:
- public static void main(String[] args) {
- ExecutorService executorService = Executors.newFixedThreadPool(3);
- for (int i = 0; i < 10; i++) {
- final int index = i;
- executorService.execute(new Runnable() {
- @Override
- public void run() {
- log.info("task:{}", index);
- }
- });
- }
- executorService.shutdown();
- }
c,newScheduledThreadPool: 创建大小无限制线程池(最大为 Integer.MAX_VALUE), 支持定时及周期性任务执行
newScheduledThreadPool 的特点是可以进行任务调度, 最常用的方法是 ScheduleAtFixedRate(基于固定时间间隔进行任务调度)和 ScheduleWithFixedDelay(基于不固定时间间隔进行任务调度, 主要取决于任务执行时间长短), demo 代码如下:
- public static void main(String[] args) {
- ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
- executorService.schedule(new Runnable() {
- @Override
- public void run() {
- log.warn("schedule run");
- }
- },3,TimeUnit.SECONDS);
- executorService.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- log.warn("schedule run");
- }
- },1,3,TimeUnit.SECONDS);
- }
d,newSingleThreadExecutor: 创建一个单线程化的线程池, 只会用唯一的线程来执行任务
newSingleThreadExecutor 将 corePoolSize 和 maximumPoolSize 都固定为 1, 线程空闲时直接销毁, 使用的 LinkedBlockingQueue.demo 代码如下:
- public static void main(String[] args) {
- ExecutorService executorService = Executors.newSingleThreadExecutor();
- for (int i = 0; i < 10; i++) {
- final int index = i;
- executorService.execute(new Runnable() {
- @Override
- public void run() {
- log.info("task:{}", index);
- }
- });
- }
- executorService.shutdown();
- }
线程池合理配置
线程池的具体配合需要按照实际情况进行调整, 以下为两条参考原则, 可先按此原则设置再根据系统负载, 资源利用情况进行调整.
a,cpu 密集型任务, 需要尽量压榨 cpu, 参考值可以设置为 ncpu+1;
b,io 密集型任务, 参考值可以设置为 2*ncpu
来源: https://www.cnblogs.com/sbrn/p/8974566.html