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();}
分析get操作可以发现,ThreadLocal对象本身并不存储值,而是作为ThreadLocalMap(TLM)的key值,而后者则存在Thread对象中,因而每个线程都有自己的TLM。、
此处可见事实上ThreadLocal的实现和Java的线程Thread是深度绑定的。
set操作
ThreadLocal的set操作源码如下:
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}
进入ThreadLocal.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);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();}
其中,Entry结构体的实现相对特殊:
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(Thread Local<?> k, Object v) {super(k);value = v;}}
它应用了WeakReference,这使得当前ThreadLocal不再被引用时,下一次GC就会被清除掉,因此tab中的元素有过时(stale)的概念。
从代码可以看到:
- ThreadLocalMap是用过线性探测方法构建的哈希表
- Entry的实现使用了
WeakReference - 每次插入后得到更新后的表大小
sz,之后算法会做约log(sz)次的启发式搜索以删除过时的表项,当无法删除且新大小超过既定阈值时,会对哈希表进行扩容。使用弱引用的原因
如果将弱引用转换为强引用,当不再使用ThreadLocal对象时,期内不得ThreadLocalMap对象中的非空Entry仍然持有ThreadLocal的强引用,如果不将它们全部删除,ThreadLocal对象就无法被回收。
使用弱引用可以防止上述循环引用,因为弱引用不会影响所引用对象的GC周期仍然存在的内存泄漏可能
当ThreadLocal对象被置null后,TLM还通过数组持有Entry的引用,此时TLM中key(ThreadLocal对象)已经消失,但value也即存储的值仍然被Entry也即TLM持有,这种情况将产生内存泄漏。
解决的方法是,在使用完ThreadLocal对象后,调用ThreadLocal#remove方法,将所有Entry数组表项主动置null并清楚对ThreadLocal的引用,从而使得Entry对象(及其包含的存储的真正的数据)能够被GC。
