引言
在前几年小编写过一篇关于线程池的总结:《线程总结》,现在回过头来看,总结的 还是比较详细的,不过当时并没有在项目中有过真实刺激的 体验,最近项目中偶然遇到了一次任务丢失的问题,我追踪了一下 代码, 发现由于不正当采用java内置线程池导致的, 应该是当时配置线程池的参数没有仔细计算导致的,关于这个问题我们后面博文在介绍,今天我们在看我java 内置 线程池代码以后,我们先动手自己写一个线程池来实现任务的提交和执行。这样我们可以更好的理解线程池的执行流程。如果读者对于java内置的线程的核心参数和执行流程不是很了解,可以点击上面链接,阅读博文。
一、在编写代码之前,我们先介绍几个核心参数的配置依据。
1.1、核心线程数量corePoolSize
核心线程数的设计需要根据任务的处理时间和每秒产生的任务数量来确定,例如执行一个任务需要0.1秒,系统百分之八十的时间没秒都会产生100个任务,那么我们想要在1秒内处理完这100个任务,就需要10个线程,此时我们就可以设计核心线程数量为10,当时实际情况不可能这么平均,所以一般我们按照2080原则设计即可,即按照百分之80的情况设计核心线程数量,剩下的百分之20可以利用最大线程数量处理。
1.2、任务队列长度(workQueue)
任务队列长度一般设计为核心线程数/单个任务执行时间*2(任务最大等待时间/s)即可,例如上面场景中,核心线程数设计为10,单个任务执行时间为0.1,则队列长度可以设计为200
1.3、最大线程数(maximumPoolSize)
最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定,例如,上述环境中,如果系统每秒最大产生的任务数量是1000个,那么最大线程数=(最大任务数-任务队列长度)* 单个任务执行时间;既最大线程数=(1000-200)* 0.1 =80;当然最大线程数和服务器的硬件配置也有很大关系
上面的 参数配置公式,都是参考作用,在实际环境中,需要根据实际服务器的配置自行调整
二、定义线程池
2.1、模拟任务类
package com.threadpoll; /** * @author zhenghao * @description: * @date 2020/6/3010:07 */ public class MyTask implements Runnable { private int id; public MyTask(int id) { this.id = id; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:" + name + "即将执行任务:" + id); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程:" + name + "完成了任务:" + id); } @Override public String toString() { return "MyTask{" + "id=" + id + '}'; } }
2.2、自定义线程类
package com.threadpoll; import java.util.ArrayList; import java.util.List; /** * @author zhenghao * @description: * @date 2020/6/3010:13 */ public class MyWorker extends Thread { private String name; private List<Runnable> tasks = new ArrayList<>(); public MyWorker(String name, List<Runnable> tasks) { super(name); this.tasks = tasks; } @Override public void run() { while (tasks.size() > 0) { Runnable r = tasks.remove(0); r.run(); } } }
2.3、自定义线程池类
package com.threadpoll; import java.util.Collections; import java.util.LinkedList; import java.util.List; /** * @author zhenghao * @description: 自定义线程池 * @date 2020/6/3015:06 */ public class MyThreadPool { /** * 任务集合 多个线程同时remove */ private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>()); /** * 当前先测试数量 */ private int num; /** * 核心线程数量 */ private int coreThreadSize; /** * 最大线程数量 */ private int maxThreadSize; /** * 队列长度 */ private int workSize; public MyThreadPool( int coreThreadSize, int maxThreadSize, int workSize) { this.coreThreadSize = coreThreadSize; this.maxThreadSize = maxThreadSize; this.workSize = workSize; } /** * @Description: 提交队列 * @author: zhenghao * @date: 2020/6/30 15:11 */ public void submitTask(Runnable r) { if (tasks.size() >= workSize) { System.out.println("任务" + r + "丢掉了"); } else { //加入队列 tasks.add(r); //执行队列 execTask(r); } } private void execTask(Runnable r) { //判断是否需要创建核心线程池 if (num < coreThreadSize) { //创建核心线程池执行 new MyWorker("核心线程池" + num, tasks).start(); num++; } else if (num < maxThreadSize) { //创建非核心线程池执行 new MyWorker("非核心线程池" + num, tasks).start(); num++; } else { System.out.println("任务" + r + "被缓存了"); } } }
2.4、测试类
package com.threadpoll; /** * @author zhenghao * @description: * @date 2020/6/3010:07 */ public class MyTask implements Runnable { private int id; public MyTask(int id) { this.id = id; } @Override public void run() { String name = Thread.currentThread().getName(); System.out.println("线程:" + name + "即将执行任务:" + id); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程:" + name + "完成了任务:" + id); } @Override public String toString() { return "MyTask{" + "id=" + id + '}'; } }
大家通过调解任务数量,也就是测试类中的循环数量,可以看到不同的效果,大家可以测试一下, 然后通过这个小demo,我们可以更好的了解线程池的执行流程。