Java Core 「17」ThreadLocal
摘要
在多线程环境下,最常用的实现线程安全的方式有如下几种:
互斥同步机制,例如 Lock,synchronized 关键字,信号量等。
非阻塞同步机制,例如自旋 + CAS。
线程独享机制,即不在多线程间共享,例如本文要学习的 ThreadLocal。
那么,ThreadLocal 是如何实现每个线程独有一份副本的?
01-ThreadLocal 线程隔离原理
当我们在某个类中使用 ThreadLocal 类型的变量时,是如何做到每个线程存储一份副本的呢?我们知道,在 Java 中,每个线程都是一个独立的 Thread 对象。要实现线程之间互相独立,肯定要在 Thread 对象上做功夫。
Thread 类中有如下的一个属性:
当我们使用 ThreadLocal 对象获得值时,一般会用如下的写法:
让我们看一下,当我们调用 get 方法时,做了什么操作:
02-示例分析
知道 ThreadLocal 实现线程间隔离的原理后,我们来分析一个实例,来具体看一下整个过程。假设我们有如下的程序:
假设有线程 t1 / t2:
当线程 t1 执行到 getConnection 时,会调用 ThreadLocal 中的 get 方法。在 get 方法中,先获取当前线程,即 t1。然后,获取 t1 的 threadLocals 映射表。此时,t1.threadLocals 为 null,即尚未初始化。然后,调用 setInitialValue,再到我们重写的方法 initialValue,返回当前线程 t1 的线程名”thread-t1“。最后,初始化 t1.threadLocals 并将 dbConnection 对象和”thread-t1”写入到 t1.threadLocals 中。
线程 t2 的执行过程与 t1 一样。因为 t1.threadLocals 和 t2.threadLocals 是两个 Thread 对象中的变量,所以互相不干扰。这样,借助 ThreadLocal 线程 t1 和 t2 实现了 dbConnection 的隔离访问。
03-ThreadLocal 导致的内存泄漏问题
ThreadLocal 有可能会导致内存泄漏的发生。从前面的学习中我们了解到,Thread 类中存在一个 ThreadLocal.ThreadLocalMap 对象,它是一个映射表,key 是对 ThreadLocal 对象(若引用),value 是一个特定类型的值。
内存泄漏出现的原因是,假如某个 ThreadLocal 对象的强引用不在了,其势必会被 GC 回收掉。但是,Thread 类中的 ThreadLocal.ThreadLocalMap 对象的生存周期与线程对象是一致的。当 ThreadLocal 对象被回收后,就无法访问其对应的 value,导致内存一致不能释放。
有一种常见的错误就是,在线程池中不恰当地使用 ThreadLocal,线程池中的线程对象一直存在,当上述情况发生时,发生内存泄漏的机率更大。
ThreadLocal 推荐的使用方法是:
每次使用完 ThreadLocal 后,就调用它的 remove 方法,移除 value
将 ThreadLocal 对象声明成 private static,确保存在强引用而不会被 GC 回收,从而能够访问到其对应的 value。
[1] 面试:为了进阿里,死磕了ThreadLocal内存泄露原因
历史文章推荐
Java Core 「16」J.U.C Executor 框架之 ScheduledThreadPoolExecutor
Java Core 「15」J.U.C Executor 框架
Java Core 「14」J.U.C 线程池 -Future & FutureTask
Java Core 「13」ReentrantReadWriteLock 再探析
Java Core 「12」ReentrantLock 再探析
版权声明: 本文为 InfoQ 作者【Samson】的原创文章。
原文链接:【http://xie.infoq.cn/article/89017e3a5512df1bc43d01f88】。文章转载请联系作者。
评论