线程池使用之自定义线程池

叁歲伎倆 2024-03-02 09:33 151阅读 0赞

目录

一:Java内置线程池原理剖析

二:ThreadPoolExecutor参数详解

三:线程池工作流程总结示意图

四:自定义线程池-参数设计分析

1:核心线程数(corePoolSize)

2:任务队列长度(workQueue)

3:最大线程数(maximumPoolSize)

4:最大空闲时间(keepAliveTime)

五:自定义线程池-实现步骤

1:编写任务类(MyTask),实现Runnable接口;

2:编写线程类(MyWorker),用于执行任务,需要持有所有任务;

3:编写线程池类(MyThreadPool),包含提交任务,执行任务的能力;

4:编写测试类(MyTest),创建线程池对象,提交多个任务测试;


一:Java内置线程池原理剖析

我们要想自定义线程池,必须先了解线程池的工作原理,才能自己定义线程池;这里我们通过观察java中ThreadPoolExecutor的源码来学习线程池的原理

  1. 构造方法:
  2. public ThreadPoolExecutor(int corePoolSize, //核心线程数量
  3. int maximumPoolSize,// 最大线程数
  4. long keepAliveTime, // 最大空闲时间
  5. TimeUnit unit, // 时间单位
  6. BlockingQueue<Runnable> workQueue, // 任务队列
  7. ThreadFactory threadFactory, // 线程工厂
  8. RejectedExecutionHandler handler // 饱和处理机制
  9. )
  10. { ... }

二: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号工作人员继续待着(池内保持核心线程数量);

三:线程池工作流程总结示意图

ab0ab8583ab943bab19bc3c073552d81.png

四:自定义**线程池-参数设计分析**

通过观察 Java 中的内置线程池参数讲解和线程池工作流程总结 , 我们不难发现 , 要设计一个好的线程池 , 就必须合理的设置线程池的 4 个参数 ; 那到底该如何合理的设计 4 个参数的值呢 ? 我们一起往下看 .

4 个参数的设计 :

1:核心线程数(corePoolSize)

核心线程数的设计需要依据任务的处理时间和每秒产生的任务数量来确定,例如:执行一个任务需要0.1秒,系统百分之80的时间每秒都会产生100个任务,那么要想在1秒内处理完这100个任务,就需要10个线程,此时我们就可以设计核心线程数为10;当然实际情况不可能这么平均,所以我们一般按照8020原则设计即可,既按照百分之80的情况设计核心线程数,剩下的百分之20可以利用最大线程数处理;

2:任务队列长度(workQueue)

任务队列长度一般设计为:核心线程数/单个任务执行时间*2即可;例如上面的场景中,核心线程数设计为10,单个任务执行时间为0.1秒,则队列长度可以设计为200;

3:最大线程数(maximumPoolSize)

最大线程数的设计除了需要参照核心线程数的条件外,还需要参照系统每秒产生的最大任务数决定:例如:上述环境中,如果系统每秒最大产生的任务是1000个,那么,最大线程数=(最大任务数-任务队列长度)*单个任务执行时间;既: 最大线程数=(1000-200)*0.1=80个;

4:最大空闲时间(keepAliveTime)

这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值,用户可以根据经验和系统产生任务的时间间隔合理设置一个值即可;

上面4个参数的设置只是一般的设计原则,并不是固定的,用户也可以根据实际情况灵活调整!

五:自定义**线程池-实现步骤**

1:编写任务类(MyTask),实现Runnable接口;

  1. /*
  2. 需求:
  3. 自定义线程池练习,这是任务类,需要实现Runnable;
  4. 包含任务编号,每一个任务执行时间设计为0.2秒
  5. */
  6. public class MyTask implements Runnable{
  7. private int id;
  8. //由于run方法是重写接口中的方法,因此id这个属性初始化可以利用构造方法完成
  9. public MyTask(int id) {
  10. this.id = id;
  11. }
  12. @Override
  13. public void run() {
  14. String name = Thread.currentThread().getName();
  15. System.out.println("线程:"+name+" 即将执行任务:"+id);
  16. try {
  17. Thread.sleep(200);
  18. } catch (InterruptedException e) {
  19. e.printStackTrace();
  20. }
  21. System.out.println("线程:"+name+" 完成了任务:"+id);
  22. }
  23. @Override
  24. public String toString() {
  25. return "MyTask{" +
  26. "id=" + id +
  27. '}';
  28. }
  29. }

2:编写线程类(MyWorker),用于执行任务,需要持有所有任务;

  1. /*
  2. 需求:
  3. 编写一个线程类,需要继承Thread类,设计一个属性,用于保存线程的名字;
  4. 设计一个集合,用于保存所有的任务;
  5. */
  6. public class MyWorker extends Thread{
  7. private String name;//保存线程的名字
  8. private List<Runnable> tasks;
  9. //利用构造方法,给成员变量赋值
  10. public MyWorker(String name, List<Runnable> tasks) {
  11. super(name);
  12. this.tasks = tasks;
  13. }
  14. @Override
  15. public void run() {
  16. //判断集合中是否有任务,只要有,就一直执行任务
  17. while (tasks.size()>0){
  18. Runnable r = tasks.remove(0);
  19. r.run();
  20. }
  21. }
  22. }

3:编写线程池类(MyThreadPool),包含提交任务,执行任务的能力;

  1. /*
  2. 这是自定义的线程池类;
  3. 成员变量:
  4. 1:任务队列 集合 需要控制线程安全问题
  5. 2:当前线程数量
  6. 3:核心线程数量
  7. 4:最大线程数量
  8. 5:任务队列的长度
  9. 成员方法
  10. 1:提交任务;
  11. 将任务添加到集合中,需要判断是否超出了任务总长度
  12. 2:执行任务;
  13. 判断当前线程的数量,决定创建核心线程还是非核心线程
  14. */
  15. public class MyThreadPool {
  16. // 1:任务队列 集合 需要控制线程安全问题
  17. private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<>());
  18. //2:当前线程数量
  19. private int num;
  20. //3:核心线程数量
  21. private int corePoolSize;
  22. //4:最大线程数量
  23. private int maxSize;
  24. //5:任务队列的长度
  25. private int workSize;
  26. public MyThreadPool(int corePoolSize, int maxSize, int workSize) {
  27. this.corePoolSize = corePoolSize;
  28. this.maxSize = maxSize;
  29. this.workSize = workSize;
  30. }
  31. //1:提交任务;
  32. public void submit(Runnable r){
  33. //判断当前集合中任务的数量,是否超出了最大任务数量
  34. if(tasks.size()>=workSize){
  35. System.out.println("任务:"+r+"被丢弃了...");
  36. }else {
  37. tasks.add(r);
  38. //执行任务
  39. execTask(r);
  40. }
  41. }
  42. //2:执行任务;
  43. private void execTask(Runnable r) {
  44. //判断当前线程池中的线程总数量,是否超出了核心数,
  45. if(num < corePoolSize){
  46. new MyWorker("核心线程:"+num,tasks).start();
  47. num++;
  48. }else if(num < maxSize){
  49. new MyWorker("非核心线程:"+num,tasks).start();
  50. num++;
  51. }else {
  52. System.out.println("任务:"+r+" 被缓存了...");
  53. }
  54. }
  55. }

4:编写测试类(MyTest),创建线程池对象,提交多个任务测试;

  1. /*
  2. 测试类:
  3. 1: 创建线程池类对象;
  4. 2: 提交多个任务
  5. */
  6. public class MyTest {
  7. public static void main(String[] args) {
  8. //1:创建线程池类对象;
  9. MyThreadPool pool = new MyThreadPool(2,4,20);
  10. //2: 提交多个任务
  11. for (int i = 0; i <30 ; i++) {
  12. //3:创建任务对象,并提交给线程池
  13. MyTask my = new MyTask(i);
  14. pool.submit(my);
  15. }
  16. }
  17. }

发表评论

表情:
评论列表 (有 0 条评论,151人围观)

还没有评论,来说两句吧...

相关阅读

    相关 定义线

    如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务; 如果当前线程池中的线程数目>=corePoolSize,则每来一个