【并发编程七:Java中的线程池(4)-线程池规范、监控、关闭】
【衔接上一章 【并发编程六:Java中的线程池(3)-线程池的应用】】
学习路线
-
- 1.12创建线程池的方式及阿里规范
- 1.13线程池的扩展
- 1.14线程池的监控
- 1.15线程池关闭
-
- shutdownNow()
- shutdown()
- 1.16线程池是否需要关闭,怎么关闭?
1.12创建线程池的方式及阿里规范
java.util.concurrent.ExecutorsExecutor, ExecutorService, ScheduledExecutorService,ThreadFactory,Callable的工厂和工具方法类,提供了一系列的静态工具方法
1.13线程池的扩展
*线程池里面提供了几个空方法(钩子方法):
*
beforeExecute、afterExecute和terminated方法,可以在任务执行前、执行后和线程池关闭前执行一些代码来扩展线程池的行为
比如,任务的平均执行时间、最大执行时间和最小执行时间等,或者输出一些日志信息、发出通知等帮助我们诊断线程池运行时出现的一些问题;通过继承并覆盖线程池的这几个空方法来实现对线程池的扩展;
代码MyThreadPoolExecutor;
import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.util.concurrent.*;/** * 自定义线程池对jdk线程池扩展 * */public class MyThreadPoolExecutor extends ThreadPoolExecutor { private static final Logger logger = LoggerFactory.getLogger(MyThreadPoolExecutor.class); private final ThreadLocal<Long> startTimeThreadLocal = new ThreadLocal<>(); public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory); } public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler); } public MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); } @Override protected void beforeExecute(Thread t, Runnable r) { super.beforeExecute(t, r); startTimeThreadLocal.set(System.currentTimeMillis()); logger.info("线程-{}-任务开始执行时间:{}", t.getName() + t.getId(), startTimeThreadLocal.get()); } @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); long endTime = System.currentTimeMillis(); long executeTime = endTime - startTimeThreadLocal.get(); logger.info("线程-{}-任务执行结束时间:{}ms", Thread.currentThread().getName() + Thread.currentThread().getId(), executeTime); } @Override protected void terminated() { super.terminated(); startTimeThreadLocal.remove(); logger.info("线程-{}-执行完毕退出.", Thread.currentThread().getName() + Thread.currentThread().getId()); //TODO 发个通知 } public static void main(String[] args) { MyThreadPoolExecutor myThreadPoolExecutor = new MyThreadPoolExecutor( 8, 16, 15, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(512), Executors.defaultThreadFactory(), new AbortPolicy()); for (int i = 0; i < 5; i++) { myThreadPoolExecutor.execute(() -> { logger.info("线程-{}-正在执行......", Thread.currentThread().getName() + Thread.currentThread().getId()); }); } //关闭线程池 //myThreadPoolExecutor.shutdown(); }}
1.14线程池的监控
线程池在运行过程中出现问题,我们怎么能排查和定位到问题所在呢?监控!
怎么监控?
- 1、线程池运行时埋点,每一次运行任务都进行采集统计;
- 2、定时采集线程池的运行数据;
- 第一种方式的监控是实时监控线程池的运行指标,对性能有一定影响;
- 第二种方式的监控是定时采集线程池运行数据,并将监控数据持久化,便于后续查看以及用于排查问题;
线程池的历史运行数据的存储,可以采用时序数据库(TSDB)进行存储,可能大部分公司主要还是用MySQL、ElasticSearch等来存储;
监控线程池时使用到的属性: - taskCount:线程池需要执行的任务数量;
- completedTaskCount:线程池在运行过程中已完成的任务数量,小于或等于taskCount;
- largestPoolSize:线程池里曾经创建过的最大线程数量,通过这个数据可以知道线程池是否曾经满过,如该数值等于线程池的最大大小,则表示线程池曾经满过;
- poolSize:线程池的线程数量,因为默认情况下核心线程不销毁,非核心线程在超时后自动销毁,所以最小也等于核心线程数;
- activeCount:获取活动的线程数;
这些数据都可以通过线程池对象给我们提供的API拿到;
1.15线程池关闭
shutdownNow()
- 1、停止接收新的任务请求;
- 2、将正在执行的任务interrupt中断;
- 3、忽略任务队列里面的任务,也就是任务队列里面的任务不会执行了;
- 4、返回没有执行的任务集合;
shutdown()
- 1、停止接收新的任务请求;
- 2、内部正在执行的任务和任务队列中等待的任务会继续执行直至完成;
- 3、等所有任务都执行完成后才会关闭线程池;
1.16线程池是否需要关闭,怎么关闭?
- 1、局部线程池一定要shutdown(在代码中声明的临时线程池);
- 2、全局公用的线程池,不能随便shutdown;
关闭就是两个方法 shutdown() / shutdownNow();
【衔接下一章【并发编程八:线程安全问题分析及锁的介绍(1)】】