【InheritableThreadLocal】InheritableThreadLocal的实现机制和原理

1  前言

上节我们看了下 ThreadLocal 的实现原理,这节我们来看下 InheritableThreadLocal 是用来干什么的呢?

我们首先看个简单的现象:

那我们把 ThreadLocal  换成 InheritableThreadLocal 的再来看下呢:

可以看到我们开辟的新线程里也能获取到了,为什么呢?接下来我们分析下。

2  源码分析

在看源码前我们先来看下 InheritableThreadLocal 类里的东西,其实很少我们看下:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }

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

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

可以看到就这么点东西,那他是怎么实现的呢?

2.1  getMap

我们看到 getMap 方法返回的是线程中的 inheritableThreadLocals 变量,我们看下:

可以看到其实就是创建 map 和 getMap 的时候初始化的是线程中的 inheritableThreadLocals 变量。

那么就有点邪门了,main 线程就是 set的时候,设置的也是设置进自己的 main线程里了啊,那么新线程 get 的时候也是从自己的线程中获取,它又怎么能拿到 main线程中的呢?是不是有这样的困惑?那是不是就是创建新线程的时候 jdk 给我动了什么手脚呢?我们看下新线程的创建:

2.2  创建线程过程

// 创建线程
public Thread(Runnable target) {
    // 调用重载方法
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
// Thread
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    // 继续调用重载 看最后一个参数是 默认是true
    init(g, target, name, stackSize, null, true);
}
// Thread 最后是这里来创建的
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    this.name = name;
    // 当前线程是谁? 是不是就是我们的 main线程
    Thread parent = currentThread();
    ...省略部分代码
    setPriority(priority);
    /**
     * 看这里 inheritThreadLocals 是不是默认为true
     * parent.inheritableThreadLocals 是不是不为空
     * 我们在创建线程前是不是 set了一个值 是不是就是放进了main线程的 inheritableThreadLocals 参数里
     * 这里是不是条件成立
     */
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        // 是不是就给当前要创建的新线程里,把 main 线程里的值初始化了一份进去 这就是新线程里为什么能拿到
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;
    /* Set thread ID */
    tid = nextThreadID();
}

这就是 InheritableThreadLocal,就是在当前创建的新线程里,判断父线程里的 inheritableThreadLocals 是不是为空,不为空的话就会复制一份进来。

2.3  createInheritedMap 

我们再来看下新线程是如何复制父线程中的:

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}
// ThreadLocalMap
private ThreadLocalMap(ThreadLocalMap parentMap) {
    // 父线程中的ThreadLocalMap 的 table
    Entry[] parentTable = parentMap.table;
    // 父线程中的ThreadLocalMap 的 table 的长度
    int len = parentTable.length;
    // 设置当前新 ThreadLocalMap 的 长度
    setThreshold(len);
    // 创建新数组
    table = new Entry[len];
    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                Object value = key.childValue(e.value);
                // 看这里创建新的 Entry 把 key、value 扔进去
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                // 设置回新的 ThreadLocalMap
                table[h] = c;
                size++;
            }
        }
    }
}

可以看到会创建一个新的 ThreadLocalMap 对象,然后把父亲的 ThreadLocalMap 遍历,进行新 Entry的 key、value的初始化,然后赋值给新的 ThreadLocalMap 。

2.3  带来的疑问

就是当我们是值类型传递的时候,是不是当 main线程更新了值,我们的新线程里是不是还是旧值的?我们看看:

引用类型的话,因为值地址传递,都是引用的同一个对象还好:

可以看到引用传递的话父子是可以同步的,值类型的话只会在创建新线程的那一刻和父线程保持一致,后续的更改不会作用到子线程里。所以大家使用的时候注意下。

3  小结

这节我们看了下 InheritableThreadLocal的实现的一个小原理,有理解不对的地方欢迎指正哈。

posted @ 2023-03-18 15:20  酷酷-  阅读(80)  评论(0编辑  收藏  举报