首先ThreadPoolExecutor中,一共提供了7个参数,每个参数都是非常核心的属性,在线程池去执行任务时,每个参数都有决定性的作用。
但是如果直接采用JDK提供的方式去构建,可见设置的核心参数最多就两个,这样就会导致对线程池的控制粒度很粗。所以在阿里规范中也推荐自己创建自定义线程池。
自定义构建线程池,可以细粒度的控制线程池,去管理内存的属性,并且针对一些参数的设置可能更好的在后期排查问题。
ThreadPoolExecutor 七大核心参数:
1 2 3 4 5 6 7 |
|
JDK提供的几种拒绝策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
线程池的核心属性就是ctl,它会基于ctl拿到线程池的状态以及工作线程个数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
|
线程池的转换方式:
execute方法是提交任务到线程池的核心方法。
execute源码解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
execute方法流程图:
addWorker方法中主要分为两大块:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
|
else if (workerCountOf(recheck) == 0) addWorker(null, false);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
|
综合上诉,Java它有一个这样的场景:
在初始化线程池的时候可能设置线程池的核心线程数为0 或者 设置allowCoreThreadTimeOut(默认false)为ture导致核心线程超时释放,存在没有核心线程的情况。
当我们把任务添加到阻塞队列之后,没有工作线程导致阻塞队列任务堆积,直到后续有新任务加入才会去创建工作线程。
1 2 3 4 5 6 7 8 |
|
综上所述,因此线程池需要构建空任务的非核心线程去处理这种情况。
线程池启动线程也是基于 Thread 对象去进行的一个 start 方法启动的,像这种它会占用jvm的栈,
所以属于GC Roots 通过垃圾回收的可达性分析算法,这种线程就不能被回收,会一直占用jvm的资源,
因此不能及时的调用 shutdown 或者 shutdownNow 方法,就可能造成内存泄漏问题!!!
当执行Thread对象的 start方法时,它会执行 Worker对象的 run 方法,
该方法中的runWorker 方法传入的是 this 就是当前的Worker对象,
就会导致启动的线程还指向了Worker对象,这个Worker对象是不能回收的,
又因为Worker对象属于线程池的内部类,
导致整个 ThreadPoolExecutor 线程池对象也不会被回收!!!
综上所述,当使用完线程池对象后,没有及时的调用关闭方法,会导致堆内存资源消耗很严重,最后会导致内存泄漏问题!
主要的难点在于任务类型无法控制,比如:
cpu密集型: cpu不断的处理任务,大量的计算等操作。
IO密集型: 不需要cpu一直调度,大多数时间都是等待结果的,如:调用第三方服务等待网络响应、等待IO响应、查询数据库等待数据库响应等等。
混合型:上面两种都会有。
大多数情况都需自己去测试,调试!没有绝对固定的一个公式。可以参考:
N thread = N cpu * U cpu * ( 1 + W / C )
线程数 = cpu的个数 * cpu的利用率 * ( 1 + 等待时间 / 计算时间 ) 注:W/C 是程序运行时 等待时间和计算时间的比值
1 * 100% * (1 + 50% / 50% )= 2
公式只是给定一个调试的初始值,需要自己后续测试调试!