线程池一百问

线程池作用

  1. 线程池,能起到复用资源,提高响应速度,提高线程可管理性的作用。

execute和submit

  1. execute和submit,都是向jdk线程池提交任务的方法,前者只能提交无返回值的任务(runnable),后者还支持提交带返回值的任务(callable)。

  2. 处理任务异常时,execute方法会直接抛出异常,需要try-catch处理。submit 会吃掉异常,并在调用future.get方法时再抛出(future接口支持传入一个阻塞等待时间)。

  3. executor 和 executorService 是jdk线程池的两个接口,前者只支持execute提交任务,后者还支持submit提交任务。实现类todo。ScheduledThreadPool xxx。

ThreadPoolExecutor

  1. ThreadPoolExecutor是jdk线程池类,构造参数有:核心线程数,最大线程数,线程保活时间,阻塞队列,线程工厂和拒绝策略。默认执行构造函数后不会初始化线程。

  2. 当向线程池提交任务后,开始创建线程执行。继续提交任务,继续创建线程,直到达到核心线程数后,不再创建新线程。新提交的任务被放入阻塞队列中。如果阻塞队列被占满,会再次创建新线程来执行任务,直到线程数达到最大线程数。如果仍有任务提交,且阻塞队列还被占满,就会执行拒绝策略。

  3. 调用 prestartAllCoreThreads() 能立即创建所有核心线程。调用 allowCoreThreadTimeOut(true) 设置核心线程空闲指定时间也会被回收。

  4. 线程保活时间,是指核心线程创建后默认不会被销毁,但大于核心线程数的线程会在闲置保活时间后被销毁。指定线程工厂,能自定义创建线程过程,如指定线程名等参数。

  5. 阻塞队列常用实现,先进先出的有界队列 ArrayBlockingQueue 和 LinkedBlockingQueue(默认大小 Integer.MAX_VALUE),优先级无界队列PriorityBlockingQueue,支持延时的无界队列 DelayQueue,无存储空间的队列 SynchronousQueue,其存入必须等待获取操作。链表结构的无界队列 LinkedTransferQueue,链表结构的双端阻塞队列LinkedBlockingDeque。

  6. 任务拒绝策略默认是丢弃任务并抛出异常,其他策略:丢弃任务但不抛出异常,丢弃等待最久的队首任务后加入新任务,使用提交任务的线程来执行等。通过实现 RejectedExecutionHandler 接口能自定义拒绝策略。

  7. ThreadPoolExecutor 创建的线程池,只有在阻塞队列占满后才会扩容线程数到最大,这在一些场景中是扩容不及时的,为此可以重写阻塞队列,让其插入元素后就返回队列满的假象,这样能触发立即扩容。tomcat 线程池就实现了类似的效果,不等阻塞队列满先扩容线程。

线程池大小

  1. 指定线程池大小,需要根据任务类型来指定,大量计算操作的cpu密集型任务,建议线程数设置为(cpu个数+1)。大量文件,网络 io操作的任务,建议线程数设置为(2*cpu个数)。多一个线程可以完成将数据从硬盘读到内存的工作。

  2. 获得硬件的 cpu 个数,使用 Runtime.getRuntime().availableProcessors()。

  3. 可以使用一个公式来计算:((w+c)/c)nu,w是等待时间,c是cpu计算时间,n是cpu核数,u是cpu利用率。

  4. 实际运用时需要通过压测和监控来确定合理大小,保证合理响应时间内TPS最高。

  5. 不同任务类型或执行时间长短差别大的任务建议单独创建线程池,避免相互影响。注意 Java8 中的 parallel stream 背后是共享的同一个 ForkJoinPool。

任务队列

  1. 线程池设置任务队列通常是有界的,只需要队列大小稍大于预估需求即可,这样既不会导致扩容滞后,也不会造成正常情况下拒绝任务。

Executors

  1. 线程池创建工具类,不推荐使用,底层仍是 ThreadPoolExecutor。

  2. newCachedThreadPool 方法,是创建最大线程数是 Integer.MAX_VALUE 和无存储阻塞队列 SynchronousQueue 的线程池。

  3. newFixedThreadPool 方法,是创建一个线程数固定,无界阻塞队列 LinkedBlockingQueue 的线程池。

  4. 上述两种线程池都非常容易出现 OOM,线程占用资源过载和阻塞队列过载。

  5. new SingleThreadPool 方法,是创建只有一个线程的线程池。todo

  6. new ScheduledThreadPool 方法,是创建固定线程数的线程池,并支持定时,周期性的执行任务;todo

优雅关闭

  1. ThreadPoolExecutor 的 shutdown 和 shutdownNow 都是关闭线程池的方法,区别是 shutdown 不接收新任务并等待队列中的任务执行完成后关闭,而 shutdownNow 是不接收新任务,并中断正在执行的任务和清空队列中的任务。

  2. 两个方法中其一被调用后,线程池处于 shutdown 状态,如果线程池中资源都销毁成功,会处于 terminate 状态。

  3. 优雅停机的实践,应该先调用 shutdown,然后多次调用 awaitTermination(time) 来阻塞等待线程池关闭,如果指定时间内该方法返回 true 表示线程池正常关闭,否则返回false,此时也不能无限期等待下去,因此可以兜底调用 shutdownNow 来尽可能做资源回收。

  4. shutdown 和 shutdownNow 都是异步关闭线程池,await 是同步阻塞等待线程池关闭。

  5. 没有绝对的无损停止,不能无限制等待 shutdown,使得整个应用无法停机,因此重点是做好任务异常中止的业务兜底,因为还要考虑硬件故障,掉电等无法触发优雅停机的场景。

线程池监控

  1. 通过继承 ThreadPoolExecutor,并重写 beforeExecute,afterExecute,terminated 方法,可以在任务执行前,任务执行后和线程池关闭前,加入特定的监控逻辑,如监控任务的平均执行时间,最大执行时间,和最小执行时间等。

  2. ThreadPoolExecutor 还提供了一些监控的属性:总任务数 taskCount,完成任务数 completedTaskCount,曾创建的最大线程数largestPoolSize 和当前存活的线程数 activeCount 等。

  3. 开源监控有 DynamicTp。

使用注意

  1. 任务异常处理:可以在任务代码使用try-catch处理,或future对象的get方法处理异常,以及为工作线程设置exceptionHandler来处理(自定义线程工厂)。

  2. 上下文中参数传递,可以使用鹰眼EagleEye,底层是threadLocal。

  3. 线程池扩展有 guava 的实现。

Fork/Join 框架

  1. Fork/Join 框架是 java7 提供的并行执行任务的框架,通过把大任务分割成若干个小任务,最终汇总每个小任务的结果后得到大任务的结果。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值