背景
-
在线程池中使用ThreadLocal会存在什么问题呢?
过程
- 引发出问题的例子演示
- 代码
- 测试类代码中添加一个静态内部类
- 测试代码类的入口函数
- 结果
- 分析
线程池中固定了3个线程。很明显,线程id为11、12和13,每个线程都抢到一个任务并执行了,这里的任务其实就是实现了Runnable接口的类,重写了run()方法。
一共是7个任务。除去每个线程都执行了一个任务,还余下4个。4 == 7 - 3,这四个任务都被线程id为11的线程抢去了。结果就出现了,我们不希望看见的一幕。因为ThreadLocalMap是线程私有的,不希望上一个线程执行结果影响到后一个线程的执行。于是就出现问题了。
这个执行结果,每次可能是不一样的,不一定每次都是线程id为11抢到任务。
- 解决方案
- 修改任务类中的run()方法执行逻辑,修改如下
- 结果
- ThreadLocal用于线程池中其他言论分析
-
在网上有读到这样的描述:他说,ThreadLocal运用到线程池中,会引发内存泄漏。原因就是线程未销毁,而此线程中的v就会一直堆积。
-
这是合理的。因为ThreadLocal有属性,private Entry[] table,而每次set的操作的时候是tab[i] = new Entry(key, value),其中这个i是这样求得的Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);这里的key是ThreadLocal实例。
-
因此,只要线程未被销毁,那么tab数组中,会一直堆积Entry实例,这个Entry实例中,有v。因为v被线程池中的线程,强引用了。不能被GC掉。
-
解决方法,是调用ThreadLocal实例的remove()方法。
小结
-
注意细节
当线程池中的一个线程执行了一次任务,而没有调用remove()方法,那么下次这个线程在执行任务的时候,就会引发业务逻辑问题。
-
ThreadLocal用在线程池中有什么问题?
线程池中的线程是没有被销毁的,线程用完后又要回收到线程池中的。如果一个线程不销毁,那么跟随这个线程的ThreadLocalMap就一直存在,上次变量的变更,下次依然在上面使用。
-
无论是ThreadLocal引发的内心泄漏,还是在线程池中存在的问题,解决方案都是,当不需要v这个值进行业务逻辑计算的时候,就一定要调用remove()方法。
-
其他结论。
如果我们启动的线程没有丢到一个线程池中的话,他会调用父线程使用的线程池,如果父线程池没有的话,最终就是名称为system的线程池。也就是说Java的线程总有一个线程池。(层层往上找线程池,最终线程池的名称是system,究竟是不是线程池呢?疑问,反正线程池名称是有了)