关于java:ThreadLocal-原理解析

4次阅读

共计 2859 个字符,预计需要花费 8 分钟才能阅读完成。

ThreadLocal 外围办法 set 和 get

ThreadLocal 外围对外办法就是 set 和 get,通过 set 存值,get 取值。

/**
 * 存值
 */
public void set(T value) {
    // 获取以后线程
    Thread t = Thread.currentThread();
    // 获取以后线程中的 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // ThreadLocalMap 曾经实例化,间接存值
        map.set(this, value);
    else
        // 实例化 ThreadLocalMap
        createMap(t, value);
}

/**
 * 取值
 */
public T get() {
    // 获取以后线程
    Thread t = Thread.currentThread();
    // 获取以后线程中的 ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // ThreadLocalMap 曾经实例化,间接取值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {@SuppressWarnings("unchecked")
            // 类型强转
            T result = (T)e.value;
            return result;
        }
    }
    // 实例化 ThreadLocalMap,并返回初始化值(ThreadLocal 默认 null)return setInitialValue();}

从下面两个办法不难看出,ThreadLocal 存取值得空间就是 ThreadLocalMap,而通过追踪 getMap 咱们能够发现,该属性是 Thread 类的成员变量,也就是说每个线程都会自带 ThreadLocalMap,这也就是各线程 ThreadLocal 中的值可能线程隔离的根本原因。

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal 中的 set 和 get 办法在调用时,如果发现 ThreadLocalMap 是空,都会进行实例化操作,代码如下所示

/**
 * 创立 ThreadLocalMap
 */
void createMap(Thread t, T firstValue) {
    // 构造函数实例化 ThreadLocalMap
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

/**
 * table 初始大小
 */
private static final int INITIAL_CAPACITY = 16;

/**
 * ThreadLocalMap 构造函数
 */
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    // 外围为 Entry 类型的数组
    table = new Entry[INITIAL_CAPACITY];
    // 计算存储的数组地位,同 HashMap 类似,通过位运算放慢计算速度
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // 结构 Entry 对象存入数组中
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

ThreadLocalMap 实例化实现后,就是往里面存值的操作,通过结构 Entry 对象来存入 ThreadLocalMap 中的 table 数组中。

private void set(ThreadLocal<?> key, Object value) {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)]) {// tab[i] 有 Entry 对象
        ThreadLocal<?> k = e.get();
        // 雷同的 ThreadLocal 对象,值笼罩
        if (k == key) {
            e.value = value;
            return;
        }
        // 原 ThreadLocal 对象曾经被回收,但因为 Entry 是弱援用
        // Entry 的 key 会被置为 null,然而 value 仍然还在
        // 这也是 ThreadLocal 可能会导致内存泄露的起因
        if (k == null) {
            // 替换因 ThreadLocal 已被回收而废除的 Entry
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // tab[i] 没有对象
    // 创立 Entry 插入
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 扩容操作
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();}

了解 set 操作之后,对于 get 操作则很容易了解。如下所示,先计算出数组下标后,间接从 table 数组中取值即可。

private Entry getEntry(ThreadLocal<?> key) {
    // 计算数组下标
    int i = key.threadLocalHashCode & (table.length - 1);
    // 依据数组下标取值
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

总结

通读 ThreadLocal 的 set 和 get 办法后,咱们就会发现,其实整个次要就波及 Thread 和 ThreadLocal 两个大类。而 ThreadLocal 中存取值的容器 ThreadLocalMap 却是 Thread 的成员变量,是线程惟一的,这就确保了每个 Thread 线程都有本人的 ThreadLocalMap 容器,相互之间是独立的,这就是 ThreadLocal 存储数据线程隔离的根本原因。然而 ThreadLocal 对象并不是线程惟一的,咱们能够实例化多个 ThreadLocal 对象来进行不同值的存储,然而要记得在用完后进行 remove 操作。因为 ThreadLocalMap 作为 Thread 的成员变量,其生命周期是和 Thread 雷同的,如果 Thread 线程没有回收,然而 ThreadLocal 却回收了,就会导致 ThreadLocalMap 中会存在很多 key 为 null 的 Entry 对象,并且其中存储的值也不会回收,如果而 ThreadLocal 对象多的话,可能会导致内存透露。特地是当 Thread 是在线程中的时候,Thread 不会进行回收操作,ThreadLocal 不进行 remove 而导致内存泄露的概率更加的大。

正文完
 0