ThreadLocal
类图
源码
set方法
public void set(T value) {
Thread t = Thread.currentThread();
// getMap(t) 就是返回t.threadLocals
// Thread的threadLocal类型为ThreadLocal.ThreadLocalMap
// ThreadLocalMap是ThreadLocal的静态内部类,是一个map
// key为ThreadLocal(软引用),value为Object
// 这里复习一下,强、软、弱、虚引用
// 强引用就是gc的时候内存不够了也不会回收,宁可抛出OOM
// 软引用就是gc的时候内存不够了才会回收
// 弱引用就是gc了就会回收,所以ThreadLocal没有回收的话会发生内存泄漏,因为软引用可能会被回收,但是value没有回收
// 虚引用不存在gc的情况,因为它就像幽灵般,随时毁灭
ThreadLocalMap map = getMap(t);
if (map != null)
// 内部就是map的结构,存的是Entry节点
map.set(this, value);
else
// 创建map并且放入第一个节点
createMap(t, value);
}
// 获取map
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 创建map
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get方法
// 如果还没有设值就获取,返回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;
}
}
// 把当前ThreadLocal的值设为null并返回null
return setInitialValue();
}
// 其实就是把null值
private T setInitialValue() {
// value = null
T value = initialValue();
Thread t = Thread.currentThread();
// 这里之所以要重新判断一次,具体我也不太懂
// 可能是
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
// 初始值
protected T initialValue() {
return null;
}
remove方法
// remove方法,挺简单的
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
/**
* Remove the entry for key.
*/
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;
// 开放地址法-线性探测法解决hash冲突
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
// 把引用清一下, Entry是继承弱引用的,所以可以调用软引用的referent
// Entry extends WeakReference<ThreadLocal<?>>
// this.referent = null;
e.clear();
// 把key和value都置为null,这样就不用怕value内存泄漏了
expungeStaleEntry(i);
return;
}
}
}
// 下一个下标(线性探测)
private static int nextIndex(int i, int len) {
// 从i找到len - 1 再到 0 ,一直循环
return ((i + 1 < len) ? i + 1 : 0);
}
// 清除陈旧的条目,并且重新hash(因为使用线性探测的,所以要处理空出来的位置的)
// expunge:清除;stale:陈旧
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
// 在这里清除了
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
// 重新hash
Entry e;
int i;
// i 获取下一个下标
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
// 返回e的k的引用
ThreadLocal<?> k = e.get();
// 如果软引用被gc了,回收value避免内存泄漏
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 计算hash,重新选择位置
int h = k.threadLocalHashCode & (len - 1);
// h != i说明之前是有hash冲突,线性探测到别的地方了
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
// 这里是重新找到新的适合的位置了
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
内存泄漏问题
ThreadLocalMap中使用的key为ThreadLocal的弱引用,而值是强引用。所以,如果ThreadLocal没有被外部强引用的情况下,在垃圾回收的时候,就会导致map中的key为null,但是value不会被清理。这样value就会无法回收,导致内存泄漏。所以使用完后最好就手动remove一下。