关于java:ThreadLocal-原理解析

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理