整改背景
通讯项目原有架构 springboot 2.5 netty4.x 构造一套接受设备端16进制报文的解析服务器
内部的每个解析协议解析都会新建一个线程执行任务,由于解析协议比较多,每次任务新建线程,
线程众多,管理这些线程的状态想到了使用线程池。
1.配置线程池
//线程池配置 /** * IO密集型任务 = 一般为2*CPU核心数(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等) * CPU密集型任务 = 一般为CPU核心数+1(常出现于线程中:复杂算法) * 混合型任务 = 视机器配置和复杂度自测而定 */ public static int cpuCodeSize = Runtime.getRuntime().availableProcessors(); public static int corePoolSize = cpuCodeSize * 2; /** * public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime, * TimeUnit com.unit,BlockingQueue<Runnable> workQueue) * corePoolSize用于指定核心线程数量 * maximumPoolSize指定最大线程数 * keepAliveTime和TimeUnit指定线程空闲后的最大存活时间 * workQueue则是线程池的缓冲队列,还未执行的线程会在队列中等待 * 监控队列长度,确保队列有界 * 不当的线程池大小会使得处理速度变慢,稳定性下降,并且导致内存泄露。如果配置的线程过少,则队列会持续变大,消耗过多内存。 * 而过多的线程又会 由于频繁的上下文切换导致整个系统的速度变缓——殊途而同归。队列的长度至关重要,它必须得是有界的,这样如果线程池不堪重负了它可以暂时拒绝掉新的请求。 * ExecutorService 默认的实现是一个无界的 LinkedBlockingQueue。 */ //LinkedBlockingQueue 列长度必须有界最大为Integer.MAX_VALUE,这里设置为1000 //handler策略模式: //1.AbortPolicy 拒绝策略:抛出运行时异常RejectedExecutionException;策略丢弃任务,并抛出异常(默认策略) //2.DiscardPolicy 拒绝策略:不能执行的任务将被丢弃;这种策略什么都没做 //3.DiscardOldestPolicy 拒绝策略:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序 //4.CallerRunsPolicy 拒绝策略:线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。 public static RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); //自定义线程池 public static Executor executor = new ThreadPoolExecutor(corePoolSize, corePoolSize + 1, 10l, TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000), handler);
2.使用线程池
这里线程池任务用到submit、execute也可以,两者区别是submit有返回值execute无返回值
Future<String> submit = pool.submit(() -> { GNSSServer.FSQLLogThreadArray.AddQueue(sql); return "success"; }); try { if (submit.get().equals("success")) { System.out.println("-------线程池submit------success-----------"); } } catch (Exception e) { System.out.println("___________线程池submit______error______________"); }
3.监视线程
把任务加入线程池之后,通过线程池api打印线程池当前运行状态
ThreadPoolExecutor pool = (ThreadPoolExecutor) ServerApplication.executor;
log.info("++++++++++++++++服务端读取完毕+++++++++++++++"); log.info("++++++++++++++++当前排队线程数"+ pool.getQueue().size()); log.info("++++++++++++++++当前活动线程数"+ pool.getActiveCount()); log.info("++++++++++++++++执行完成线程数"+ pool.getCompletedTaskCount()); log.info("++++++++++++++++总线程数(排队线程数 + 活动线程数 + 执行完成线程数)"+ pool.getTaskCount());
4.线程池工作流程图
5.线程池线程生命周期
设:我们有一个coreSize=10,maxSize=20,keepAliveTime=60s,queue=40
1、池初始化时里面没有任何线程。
2、当有一个任务提交到池就创建第一个线程。
3、若继续提交任务,有空闲线程就调拨空闲线程来处理任务?若没有线程空闲则再新建一个线程来处理,如此直到coreSize。【预热阶段】
4、若继续提交任务,有空闲线程就调拨空闲线程来处理任务,如果没有空闲线程(10个)则将任务缓存到queue中排队等待。
5、若继续提交任务,而已有线程不空闲,且queue也满了,则新建线程,并将最新的任务优先提交给新线程处理。
6、若继续提交任务,且所有线程(20个)仍不空闲,queue也是满的,此时就会触发池的拒绝机制。
8、一旦有任何线程空闲下来就会从queue中消费任务,直到queue中任务被消费完。
9、当总忙碌线程个数不超过coreSize时,闲暇线程休息keepAliveTime过后会被销毁。
10、而池中一直保留coreSize个线程存活。