ThreadLocal源码解读

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一下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值