一:参数分析
我们要想自定义线程池,必须先了解线程池的工作流程,才能自己定义线程池。下图是ThreadPoolExecutor的构造方法。
我们可以通过下面的场景理解ThreadPoolExecutor中的各个参数;
a客户(任务)去银行(线程池)办理业务,;但银行刚开始营业,窗口服务员还未就位(相当于线程池中初始线程数量为0)于是经理(线程池管理者)就安排1号工作人员(创建1号线程执行任务)接待a客户(创建线程);
在a客户业务还没办完时,b客户(任务)又来了,于是经理(线程池管理者)就安排2号工作人员(创建2号线程执行任务)接待b客户(仅创建了一个新的线程);假设该银行总共就2个窗口(核心线程数量是2);
紧接着在a.b客户都没有结束的情况下c客户来了,于是经理(线程池管理者)就安排c客户先坐到银行大厅的座位上(空位相当于是任务队列)等候并告知他:如果1、2号工作人员空出,c就可以前去办理业务;
此时d客户又到了银行(工作人员都在忙;大厅座位也满了)于是经理赶紧安排临时工(新创建的线程)在大堂站着,手持pad设备给d客户办理业务;假如前面的业务都没有结束的时候e客户又来了此时正式工作人员都上了,临时工也上了座位也满了(临时工加正式员工的总数量就是最大线程数),于是经理只能按《超出银行最大接待能力处理办法》(饱和处理机制)拒接接待e客户;
最后进来办业务的人少了,大厅的临时工空闲时间也超过了1个小时(最大空闲时间)经理就会让这部分空闲的员工人下班.(销毁线程)
但是为了保证银行银行正常工作(有一个allowCoreThreadTimeout变量控制是否允许销毁核心线程;默认false),即使正式工闲着,也不得提前下班所以1、2号工作人员继续待着(池内保持核心线程数量);
线程池工作流程总结示意图:
通过观察Java中的内置线程池参数讲解和线程池工作流程总结,我们不难发现,要设计一个好的线程池
就必须合理的设置线程池的4个参数。
-
核心线程数(corePoolSize):核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定,例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均所以我们一般按照8O20原则设计即可,既按照百分之80的情况设计核心线程数,剩下的百分之20可以利用最大线程数处理;
-
任务队列长度(workQueue):任务队列长度一般设计为:核心线程数/单个任务执行时间*2即可;例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200;
-
最大线程数(maximumPoolSize):最大线程数的设计除了需要参照核心线程数的条件外;还需要参照系统每秒产生的最大任务数决定:例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,最大线程数=(最大任务数-任务队列长度)*单个任务执行时间;既:最大线程数=(1000-200)*0.1=80个;
-
最大空闲时间(keepAliveTime):这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可;
二:代码实现
1:编写任务类(MyTask),实现Runnable接口;
2:编写线程类(MyWorker),用于执行任务,需要持有所有任务;
3:编写线程池类(MyThreadPool),包含提交任务,执行任务的能力;
4:编写测试类(MyTest),创建线程池对象,提交多个任务测试;
MyTask.java:
package com.eyes.thread.threadPool.demo1;
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 +
'}';
}
}
MyWorker.java:
package com.eyes.thread.threadPool.demo1;
import java.util.List;
public class MyWorker extends Thread{
private String name;
private List<Runnable> tasks;
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();
}
}
}
MyThreadPool.java:
package com.eyes.thread.threadPool.demo1;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class MyThreadPool {
// 1.任务队列 集合 需要控制安全问题
private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
// 2.当前线程数量
private int num;
// 3.核心线程数
private int corePoolSize;
// 4.最大线程数
private int maxSize;
// 5.任务队列的长度
private int workSize;
public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
this.corePoolSize = corePoolSize;
this.maxSize = maxSize;
this.workSize = workSize;
}
// 1:提交任务
public void submit(Runnable r) {
// 判断当前集合中任务的数量是否超出了最大任务数量
if (tasks.size() >= workSize) {
System.out.println("任务:" + r + "被丢弃了...");
} else {
tasks.add(r);
// 执行任务
execTask(r);
}
}
// 2.执行任务
private void execTask(Runnable r) {
// 判断当前线程池中的线程总数量是否超出了核心数
if (num < corePoolSize) {
new MyWorker("核心线程:" + num, tasks).start();
num++;
} else if (num < maxSize){
new MyWorker("非核心线程:" + num, tasks).start();
num++;
} else {
System.out.println("任务:" + r + "被缓存了...");
}
}
}
MyTest.java:
package com.eyes.thread.threadPool.demo1;
public class MyTest {
public static void main(String[] args) {
// 1.创建线程池类对象
MyThreadPool pool = new MyThreadPool(2, 4, 20);
// 2.提交多个任务
for (int i = 0; i < 30; i++) {
// 3.创建任务对象并提交给线程池
MyTask my = new MyTask(i);
pool.submit(my);
}
}
}
运行结果:
如果有兴趣了解更多相关内容,欢迎来我的个人网站看看:瞳孔的个人空间