ThreadLocal是一种变量类型,根据当前的线程,变量的value也会不一样,也可以理解成每个线程内都有一个变量的副本,很多人说ThreadLocal就是一个key为线程的map,看了下源码,我是这样理解的(可以看下jdk8的官方文档 https://docs.oracle.com/javase/8/docs/api/index.html)
public class ThreadLocal<T> {
public T get() {...}
public void set(T value) {...}
}
说map的大体上没问题,但是Threadlocal考虑的显然是比map全面且高效
public T get() {
// 获取当前的线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//map不为空,把当前的ThreadLocal变量作为key,从map获取entry节点
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// map为空时,获取初始值
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
// threadlocal 内部方法,看得出thread本身有一个threadLocalMap类型的成员变量
return t.threadLocals;
}
注意,是Thread类内有ThreadLocal.ThreadLocalMap threadLocals,ThreadLocalMap是ThreadLocal的内部类,下面是他们的层级结构,
map里有一个Entry类型的数组table,Entry节点继承WeakReference弱引用
public class ThreadLocal<T>{
...
static class ThreadLocalMap {
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
贴一段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);
}
这段总的来说,根据ThreadLocal的threadLocalHashCode 去拿到数组下标,判断是否为预期的key,如果发生hash碰撞,那就执行getEntryAfterMiss,这个源码不贴了,就是一个递归拿值。
简单说下set,其实也是一个从设置map key-value的过程
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
代码简洁,有map就set,没有就先创建
private void set(ThreadLocal<?> key, Object value) {
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();
}
这段代码其实很好理解,比hashmap的set简单,根据key的hashcode计算下标,处理hash碰撞
再补充一个点,说下ThreadLocalMap中的Entry为什么继承了WeakReference?
java中一共有四种引用,强软弱虚,WeakReference就是里面的弱引用。对引用不懂的可以看下这篇文章
https://www.cnblogs.com/czx1/p/10665327.html
这种引用的特点就是,弱引用的对象生命周期短,Gc碰到就回收,回收之后通过引用获取的值是null,跟强引用不同在于,比如一个userInfo,userinfo=null,会把userinfo设置为null,但是不会回收,直到线程结束才会回收整个map;弱引用不同,通过ThreadLocal.remove()同时会释放map中的entry,清除彻底;userinfo=null只释放掉了userinfo,没有释放entry