ThreadLocal 实现原理
ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需要存储的 Object。
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是, ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。
ThreadLocal的源码分析
ThreadLocalMap
ThreadLocalMap与Thread的关系
在ThreadLocal中,用来存储线程的局部变量的
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
t.threadLocals实际上就是访问Thread类中的ThreadLocalMap这个成员变量
public class Thread implements Runnable{
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
从代码可知,每一个线程都有自己单独的ThreadLocalMap实例,而对应这个线程的所有本地变量都会保存到这个map内。
ThreadLocalMap的创建
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
-
Thread t 是通过Thread.currentThread()来获取的表示当前线程,然后对当前线程中的threadLocals进行初始化。
-
this 表示当前对象的引用,也就是ThreadLocal声明的对象。那么多个线程对应同一个ThreadLocal实例,怎么对每一个ThreadLocal对象做区分呢?
ThreadLocalMap的存储结构
ThreadLocalMap是一个静态内部类,内部定义了一个Entry对象用来真正存储数据。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//构造一个Entry数组,并设置初始大小
table = new Entry[INITIAL_CAPACITY];
//计算Entry数据下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//将`firstValue`存入到指定的table下标中
table[i] = new Entry(firstKey, firstValue);
//设置节点长度为1
size = 1;
//设置扩容的阈值
setThreshold(INITIAL_CAPACITY);
}
}
Entry继承 WeakReference带来的好处与坏处?
get
从当前线程中获取ThreadLocalMap,查询当前ThreadLocal变量实例对应的Entry,如果不为null,返回获取的value。如果map为null,进行初始化。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
set
设置当前线程的线程局部变量的值
public void set(T value) {
//获取当前执行的线程
Thread t = Thread.currentThread();
//根据当前线程得到当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//map不为空,当前线程已经构造过ThreadLocalMap,直接将值存储到map中
if (map != null)
map.set(this, value);
else
//map为空,调用 createMap
createMap(t, value);
}
如果map不为空,说明当前线程已经有了一个ThreadLocalMap实例,直接将当前value设置到ThreadLocalMap中
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//从i开始往后一直遍历到数组最后一个Entry
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果key相等,覆盖
if (k == key) {
e.value = value;
return;
}
//如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
- map中的key为空,会清理key=null的数据。Entry的key为弱引用,如果key为空,说明ThreadLocal这个对象被GC回收了。replaceStaleEntry方法进行清除。
- 扩容过程会清除无效的Entry。cleanSomeSlots方法进行清除。
remove
从Entry[]中删除指定的key。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
ThreadLocal的内存泄漏
内存泄漏原因
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
建议的使用方法是:
-
将ThreadLocal变量定义成private static的。
这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露
-
每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
为什么使用弱引用
从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?
我们先来看看官方文档的说法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对非常大和长时间的用途,哈希表使用弱引用的 key。
下面我们分两种情况讨论:
-
key 使用强引用:
引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
-
key 使用弱引用:
引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
比较两种情况,我们可以发现:
由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
ThreadLocal内存泄漏的根源是:
由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
场景
常见的ThreadLocal使用场景为 用来解决数据库连接、Session管理等。
数据库连接
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
Session管理
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
传递变量
public class BizDataContext {
public static final String KEY_NAMESPACE = BizDataContext.class.getName();
private static ThreadLocal<BizDataContext> bizDataContext;
private Map<String, Object> store = new HashMap();
static {
bizDataContext = new ThreadLocal(){
//初始化自定义上下文数据
protected BizDataContext initialValue(){
return new BizDataContext();
}
};
}
private static BizDataContext getBizDataConetxt(){
return (BizDataContext) bizDataContext.get();
}
private static Map<String, Object> getStore(){
return getBizDataConetxt().store;
}
/***
* 对数据存储map store的操作
* @param key
* @return
*/
private static Object get(String key){
return getStore().get(KEY_NAMESPACE+key);
}
private static Object set(String key, Object value) {
return getStore().put(KEY_NAMESPACE+key, value);
}
public static Object remove(String key){
return getStore().remove(KEY_NAMESPACE+key);
}
public static void clear(){
getStore().clear();
}
/***
* 存储value
* @param key
* @param value
*/
public static void setValue(String key,String value){
set(key, value);
}
public static String getValue(String key){
return (String)get(key);
}
}