ThreadLocal源码解读,干就完事了

ThreadLocal 源码

ThreadLocal 提供了线程局部变量,比如我在线程A通过ThreadLocal Set一个值进去,那么在这个线程的执行过程中,我们在任何方法里都能取到这个值。

如果在这个线程中开辟的子线程里面,是取不到这个值的,ThreadLocal只能作用于当前线程。

这就涉及到了ThreadLocal的原理,虽然我们可以在任何地点都能new一个ThreadLocal出来,但是通过ThreadLocal Set的变量最终是存放在当前线程的threadLocals的Map结构中,Map的key是ThreadLocal的实例,我们可以从源码里面看到相关的处理

private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    // 如果tab[i]不为空
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        // 存在引用,直接替换值
        if (k == key) {
            e.value = value;
            return;
        }
        // 替换过期的k
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // 为空,直接存放,增加size
    tab[i] = new Entry(key, value);
    int sz = ++size;
    //检测是否需要调整table大小
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        //重新调整大小
        rehash();
}
public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 从当前线程获取 ThreadLocalMap 
    // 一个Map结构,Key=>ThreadLocal对象,value=>通过ThreadLocal Set进去的值
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // ***** 核心方法
        // 从当前线程的ThreadLocalMap获取Entry,这里的参数this就是当前ThreadLocal对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        // 不为空就返回
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果Map为空,这里会创建一个ThreadLocalMap
    return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
    // 通过threadLocalHashCode 和 表长度-1 的与运算,得出table的下标
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // e.get()说明:Entry继承了WeakReference,所以e.get() 获取的是e的引用对象,也就是key
    if (e != null && e.get() == key)
        return e;
    else
        // 如果找不到,就从e的位置继续向后找
        return getEntryAfterMiss(key, i, e);
}
//查找对象
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        //获取Entry的引用对象ThreadLocal
        ThreadLocal<?> k = e.get();
        // 相同直接返回
        if (k == key)
            return e;
        // 如果k为空,这是一个过时的数据,执行清理
        if (k == null)
            expungeStaleEntry(i);
        else
            // 移动下标,继续查找,一直找到链表的头,如果没有就跳出循环
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
// 清理过期数据,重新计算hash值
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // 直接清理过时的数据
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    //长度减一
    size--;

    // 继续查找,清理 并 重新计算hash, 直到遇到null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        // 如果为空,就清理,并将size减1
        ThreadLocal<?> k = e.get();
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            // 不为空 ,就重新计算hash
            int h = k.threadLocalHashCode & (len - 1);
            // 新计算的hash于当前Hash不一致时
            if (h != i) {
                tab[i] = null;
                // 与 Knuth 6.4 算法 R 不同,我们必须扫描直到为空,因为多个条目可能已经过时。
                // 如果h位置不为空时,需要重新计算,直到h位置为空
                while (tab[h] != null)
                    h = nextIndex(h, len);
                //将i位置的e对象放入h位置
                tab[h] = e;
            }
        }
    }
    return i;
}

针对threadLocalHashCode的说明

ThreadLocals 的是实现依赖于每个线程中(Thread.threadLocals 和inheritableThreadLocals)的哈希映射。

ThreadLocal 对象充当键,通过 threadLocalHashCode 进行搜索。

这是一个自定义哈希代码(仅在 ThreadLocalMaps 中有用),它消除了在相同线程使用连续构造的 ThreadLocals 的常见情况下的冲突,同时在不太常见的情况下保持良好行为。

InheritableThreadLocal 原理

InheritableThreadLocal 继承了 ThreadLocal

重要的方法

ThreadLocalMap getMap(Thread t) {
    return t.inheritableThreadLocals;
}

ThreadLocalgetMap获取的是Thread.threadLocalsInheritableThreadLocal 获取的是Thread.inheritableThreadLocals

这就是ThreadLocalInheritableThreadLocal 最大的不同

如何实现子线程里有父线程的对象?

主要取决与创建线程时的初始化方法

java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long)

private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    // 不相干的代码删掉了,不是方法的全部源码
    
    //当前线程,也就是创建线程的线程 = 父线程
    Thread parent = currentThread();
    //inheritThreadLocals = true,并且父线程的inheritableThreadLocals不为空,就复制到子线程中
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}

总结:

1.ThreadLocalInheritableThreadLocal操作的线程变量是不一样的,所以这两个变量的值是不共享。

2.InheritableThreadLocal通过重新getMap的方式,将ThreadLocalthreadLocals替换为inheritableThreadLocals,其他逻辑完全一样。

3.InheritableThreadLocal在子线程创建的时候进行同步,实现代码在Thread#init方法中

4.ThreadLocal的生命周期和Thread一样长,如果不及时remove掉,会造成内存泄漏

posted @ 2021-06-25 14:29  InkYi  阅读(76)  评论(0编辑  收藏  举报