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 而导致内存泄露的概率更加的大。


lxKLj
0 声望0 粉丝

学无止境,笨鸟先飞