ThreadLocal理解三:在线程池中使用存在的问题及其解决方案

1,621 阅读3分钟

背景

  1. 推荐阅读ThreadLocal 理解一:基础知识及其应用

  2. 推荐阅读ThreadLocal理解二:内存泄漏过程分析及其解决方案

  3. 在线程池中使用ThreadLocal会存在什么问题呢?

过程

  • 引发出问题的例子演示
  1. 代码 在这里插入图片描述
  2. 测试类代码中添加一个静态内部类 在这里插入图片描述
  3. 测试代码类的入口函数 在这里插入图片描述
  4. 结果 在这里插入图片描述
  5. 分析

线程池中固定了3个线程。很明显,线程id为11、12和13,每个线程都抢到一个任务并执行了,这里的任务其实就是实现了Runnable接口的类,重写了run()方法。

一共是7个任务。除去每个线程都执行了一个任务,还余下4个。4 == 7 - 3,这四个任务都被线程id为11的线程抢去了。结果就出现了,我们不希望看见的一幕。因为ThreadLocalMap是线程私有的,不希望上一个线程执行结果影响到后一个线程的执行。于是就出现问题了。

这个执行结果,每次可能是不一样的,不一定每次都是线程id为11抢到任务。

  • 解决方案
  1. 修改任务类中的run()方法执行逻辑,修改如下 在这里插入图片描述
  2. 结果 在这里插入图片描述
  • ThreadLocal用于线程池中其他言论分析
  1. 在网上有读到这样的描述:他说,ThreadLocal运用到线程池中,会引发内存泄漏。原因就是线程未销毁,而此线程中的v就会一直堆积。

  2. 这是合理的。因为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实例。

  3. 因此,只要线程未被销毁,那么tab数组中,会一直堆积Entry实例,这个Entry实例中,有v。因为v被线程池中的线程,强引用了。不能被GC掉。

  4. 解决方法,是调用ThreadLocal实例的remove()方法。

小结

  1. 注意细节

    当线程池中的一个线程执行了一次任务,而没有调用remove()方法,那么下次这个线程在执行任务的时候,就会引发业务逻辑问题。

  2. ThreadLocal用在线程池中有什么问题?

    线程池中的线程是没有被销毁的,线程用完后又要回收到线程池中的。如果一个线程不销毁,那么跟随这个线程的ThreadLocalMap就一直存在,上次变量的变更,下次依然在上面使用。

  3. 无论是ThreadLocal引发的内心泄漏,还是在线程池中存在的问题,解决方案都是,当不需要v这个值进行业务逻辑计算的时候,就一定要调用remove()方法。

  4. 其他结论。

    如果我们启动的线程没有丢到一个线程池中的话,他会调用父线程使用的线程池,如果父线程池没有的话,最终就是名称为system的线程池。也就是说Java的线程总有一个线程池。(层层往上找线程池,最终线程池的名称是system,究竟是不是线程池呢?疑问,反正线程池名称是有了)