前言
通过分析threadLocal的源码,来解答问题: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();
}
实际上ThreadLocal中的值是存储在ThreadLocalMap这个类中的。继续进入getMap方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
第一次执行get方法时,还没有ThreadLocalMap实例,进入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;
}
该方法就是设置ThreadLocal的初始值。此时ThreadLocalMap实例仍然不存在,进入createMap方法:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
因此,可以看到,ThreadLocalMap实例是放在Thread实例的成员变量threadLocals中的。 ThreadLocalMap是ThreadLocal的静态内部类。进入ThreadLocalMap的构造方法:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
其实这里的套路就跟Map比较像了。ThreadLocalMap内部维护了一个数组,数组中的元素是Entry对象。数组的下标就是当前threadLocal变量的hash值对数组长度取模。
获取threadLocal值的方法:
ThreadLocalMap.Entry e = map.getEntry(this);
就是传入当前ThreadLocal实例去获取的。ThreadLocalMap内部计算hash值,得到数组的索引,获取到对应的Entry对象返回。
Entry
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
注意entry对象弱引用了threadlocal对象。为什么这里要使用弱引用:
弱引用的意思就是,如果这个被弱引用的对象没有强引用了,那么这个对象就会在下一次GC的时候被回收掉,无论内存是否足够。
所以entry中弱引用了threadlocal的目的是:使得threadlocal在没有强引用之后就被回收掉,避免因为线程得不到销毁而threadlocal对象无法被回收。 就是把threadlocal的生命周期和线程生命周期给解绑了。(具体实现方式往后看set方法的实现)。
比如说,这个线程里面有很多个threadlocal对象,而且这个线程执行很久,前面的一些threadlocal对象其实已经没有被强引用了,那么使用弱引用就可以先把这个线程中没用的threadlocal对象回收掉。 如果entry没有弱引用threadlocal,直接放了对应的value,那么整个线程中所有的threadlocal的变量值都在线程被销毁的时候才统一回收。那么就是把threadlocal和线程的生命周期给绑定在一起了。
set方法
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法也比较简单,就是以当前Threadlocal变量的hash值对数组长度取模作为数组下标,将value存入ThreadLocalMap中的数组里面的。
再继续进入ThreadLocalMap中的set方法:
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);
//需要不断循环,解决hash冲突(具体实现暂时不展开了)
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
if (k == key) {
e.value = value;
return;
}
//这里就体现了弱引用的好处了。这个数组位置的entry不为空,但k为空,说明这个值被垃圾回收了,那么这个位置就可以被新的threadlocal变量给替换了
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
//cleanSomeSlots方法也是用来清除那些entry不为空,但其中的threadlocal变量已经被回收的对象。
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
threadLocal是如何实现线程隔离的
实现线程隔离也就是将ThreadLocalMap实例维护在每个线程实例的成员变量threadLocals中。 不同的线程去获取当前线程的threadLocal变量时,是通过Thread.currentThread()获取当前线程所维护的ThreadLocalMap实例的。因为Thread.currentThread()是线程隔离的,自然threadLocal也就是线程隔离的。
每个线程可以创建多个ThreadLocal实例来实现多个变量的线程隔离
为什么可以创建多个ThreadLocal实例?因为ThreadLocalMap其实就是一个Map,内部维护了一个table,table就是以每个ThreadLocal实例的hash值作为数组索引的。当然可以维护多个ThreadLocal。
注意
使用ThreadLocal一般都是声明在静态变量中。如果不断的创建ThreadLocal而没有调用remove方法,就会导致内存泄漏,特别是在高并发的web容器当中。
来源:oschina
链接:https://my.oschina.net/u/2763531/blog/2979309