ThreadLocal理解及使用
threadlocal是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。
ThreadLocal
//set 方法
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//实际存储的数据结构类型
ThreadLocalMap map = getMap(t);
//如果存在map就直接set,没有则创建map并set
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//getMap方法
ThreadLocalMap getMap(Thread t) {
//thred中维护了一个ThreadLocalMap
return t.threadLocals;
}
//createMap
void createMap(Thread t, T firstValue) {
//实例化一个新的ThreadLocalMap,并赋值给线程的成员变量threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
通过set方法可以看到 当前线程保存了一个ThreadLocalMap类型的threadLocals,已threadLocal为key存放了当前数据value.
//ThreadLocal中get方法
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();
}
//ThreadLocalMap中getEntry方法
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
ThreadLocalMap
set方法
//Entry为ThreadLocalMap静态内部类,对ThreadLocal的若引用
//同时让ThreadLocal和储值形成key-value的关系
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//ThreadLocalMap构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//内部成员数组,INITIAL_CAPACITY值为16的常量
table = new Entry[INITIAL_CAPACITY];
//位运算,结果与取模相同,计算出需要存放的位置
//threadLocalHashCode比较有趣
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
每个线程Thread持有一个ThreadLocalMap类型的实例threadLocals,结合此处的构造方法可以理解成每个线程Thread都持有一个Entry型的数组table,而一切的读取过程都是通过操作这个数组table完成的。
Entry是一个弱引用,threadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉。比如线程池的存在会导致内存泄露。
当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。
Remove()方法
private void remove(ThreadLocal<?> key) {
//使用hash方式,计算当前ThreadLocal变量所在table数组位置
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//再次循环判断是否在为ThreadLocal变量所在table数组位置
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//调用WeakReference的clear方法清除对ThreadLocal的弱引用
e.clear();
//清理key为null的元素
expungeStaleEntry(i);
return;
}
}
ThreadLocalMap的set方法,当key为null时清除value.
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
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);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
避免内存泄露的方法:每次使用完ThreadLocal都调用它的remove()方法清除数据。
ThreadLocal的使用场景
最常见的场景是在拦截器interceptor解析用户信息,并将用户信息放入到ThreadLocal中。
public class UserContext {
private static ThreadLocal<User> localUser = ThreadLocal.withInitial(() -> {
return new User();
});
public static void setUser(User user) {
localUser.set(user);
}
public static User getUser() {
return localUser.get();
}
Spring中的RequestContextHolder就是使用了TreadLocal存放信息。
基于Session的会话管理也用到了TreadLoca,如shiro的 subject 内部用到了Treadlocal。