今天看了一篇关于线程池源码的文章,写的很棒,在此推荐给大家,同时记录一下方便自己回看【线程池之ThreadPoolExecutor线程池源码分析笔记】,因源码部分早已弄懂,所以我更多关注的是实际使用时的需注意事项。
一、创建线程池时候要指定与业务相关的名字,以便于追溯问题
我们都知道,线程池中的线程最终是通过ThreadFactory产出的,那么要改线程名字,势必要去了解下ThreadFactory的源码,话不多说,下面贴出源码:
由上代码 DefaultThreadFactory 的实现可知:
- 代码(1)poolNumber 是 static 的原子变量用来记录当前线程池的编号,它是应用级别的,所有线程池公用一个,比如创建第一个线程池时候线程池编号为1,创建第二个线程池时候线程池的编号为2,这里 pool-1-thread-1 里面的 pool-1 中的 1 就是这个值。
- 代码(2)threadNumber 是线程池级别的,每个线程池有一个该变量用来记录该线程池中线程的编号,这里 pool-1-thread-1 里面的 thread - 1 中的 1 就是这个值。
- 代码(3)namePrefix是线程池中线程的前缀,默认固定为pool。
- 代码(4)具体创建线程,可知线程的名称使用 namePrefix + threadNumber.getAndIncrement() 拼接的。
从上知道我们只需对 DefaultThreadFactory 的代码中 的初始化做手脚,当需要创建线程池是传入与业务相关的 namePrefix 名称就可以了,代码如下(为方便直接拷贝使用,我已把个人信息注释消除):
讯享网
那么创建线程池就可以指定线程名了,测试代码如下:
java高级特性多线程基础头歌
. 运行结果如下,一目了然,出现异常可以准确知道是哪种线程报的错:

后续补充:
当然还有更简单的方法,我们可以直接使用Spring封装好的Executor来创建线程池,所有属性根据需要来设置即可,创建好后直接交由Spring管理,举例如下:
讯享网
二、如果在线程池中用了ThreadLocal,要警惕内存泄露问题
看下面内存泄露的例子
- 代码(1)创建了一个核心线程数和最大线程数为 5 的线程池,这个保证了线程池里面随时都有 5 个线程在运行。
- 代码(2)创建了一个 ThreadLocal 的变量,泛型参数为 LocalVariable,LocalVariable 内部是一个 Long 数组。
- 代码(3)向线程池里面放入 50 个任务
- 代码(4)设置当前线程的 localVariable 变量,也就是new了一个LocalVariable 放入当前线程的 threadLocals 中。
由于没有调用线程池的 shutdown 或者 shutdownNow 方法所以线程池里面的用户线程不会退出,进而 JVM 进程也不会退出。
重点是在线程池shutDown之前ThreadLocal可能产生内存泄露。
可以看到在没有对ThreadLocal进行清除时,也就是没有执行上面的方法,Jconsole的堆内存使用量如下:

在放开注释,执行方法清除ThreadLocal值后,Jconsole的堆内存情况如下:

结果一目了然,当主线程处于休眠时候,运行结果一的进程占用了大概 77M 内存,运行结果二则占用了大概 25M 内存,可知运行代码一时候内存发生了泄露…
原因:
运行结果一的代码,在设置线程的 localVariable 变量后没有调用 localVariable.remove()方法,导致线程池里面的 5 个线程的 threadLocals 变量里面的 new LocalVariable() 实例没有被释放,虽然线程池里面的任务执行完毕了,但是线程池里面的 5 个线程会一直存在直到 JVM 进程被杀死。
这里需要注意的是由于 localVariable 被声明了 static,虽然线程的 ThreadLocalMap 里面是对localVariable的弱引用,localVariable也不会被回收。
运行结果二的代码由于线程在设置 localVariable 变量后及时调用了 localVariable.remove() 方法进行了清理,所以不会存在内存泄露。
总结:线程池里面设置了 ThreadLocal 变量一定要记得及时清理,因为线程池里面的核心线程是一直存在的,如果不清理,那么线程池的核心线程的 threadLocals 变量一直会持有 ThreadLocal 变量。
三、建议线程池所有参数自定义,防止OOM等问题
这个就不展开讲了,在阿里代码规范里也有说明,线程池要自定义,不要用原生的,比如CachedThreadPool:最大线程范围是Int的最大值,可能会创建大量线程导致OOM。
另外核心线程和最大线程的取舍也是有讲究的,可以根据和来设置。
还有拒绝策略也有讲究,一般如果一定要执行任务,没什么问题的话,就在拒绝策略发生时,抛给调用线程执行,如果其他情况就要换策略,这个还是具体情况具体分析的…
总结:
- 创建线程池需传自定义的ThreadFactory来实现线程名的定制
- 线程池中用完ThreadLocal要主动清除,防止内存泄露
- 线程池自定义,也就是所有参数需视情况来自定义。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/5757.html