ThreadLocal 源码解析 - litter-fish/ReadSource GitHub Wiki

ThreadLocal的作用 提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

ThreadLocal 内部结构图

29e2e9208ac538a9edab4a0ed6b7ca4b.png

Thread 内的 threadLocals 属性值 7b4d4b0312da5a0c3ec77c4372b71ead.png

  • 每个Thread线程内部都有一个Map。
  • Map里面存储线程本地对象(key)和线程的变量副本(value)
  • Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

Thread内部的Map

public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocalMap类图

d6cb3edbe408eea4a9f5ec7922f40a86.jpeg

构造函数

/**
 * Creates a thread local variable.
 * @see #withInitial(java.util.function.Supplier)
 */
public ThreadLocal() {
}

设置ThreadLocal 初始值 initialValue

protected T initialValue() {
    return null;
}

使用方式

public class TestThreadLocal {
    private static final ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return Integer.valueOf(1);
        }
    };
}

get 函数

从当前线程的ThreadLocal.ThreadLocalMap对象中取出对应的ThreadLocal变量所对应的值

// 从当前线程的ThreadLocal.ThreadLocalMap对象中取出对应的ThreadLocal变量所对应的值
public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 根据当前线程获取一个Map
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 以ThreadLocal的引用作为key, 在Map中获取对应的value
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            // 返回e.value
            T result = (T)e.value;
            return result;
        }
    }
    // initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map
    return setInitialValue();
}

初始化操作

// 如果不存在map这创建map并进行初始化操作
private T setInitialValue() {
    // 自定义的初始化赋值
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

set 方法

向当前线程的ThreadLocal.ThreadLocalMap类型的成员变量threadLocals中设置值,key是this,value是我们指定的值

// 向当前线程的ThreadLocal.ThreadLocalMap类型的成员变量threadLocals中设置值,key是this,value是我们指定的值
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

获取Thread 类的 threadLocals 属性

// 获取Thread 类的 threadLocals 属性
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

Thread 类的 threadLocals 属性

public class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocalMap set方法,设置与key关联的值。

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)]) {
        ThreadLocal<?> k = e.get();

        // Entry 是存储过的 ThreadLocal,就直接将以前的数据覆盖掉
        if (k == key) {
            e.value = value;
            return;
        }
        //  Entry 是一个过时的Entry ?????
        if (k == null) {
            // 替换过时的Entry
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // 找到一个空的
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 清除部分的过时Entry,如果清除不成功,并且大于等于负载阈值 threshold (当前size的2/3)的时候就会 rehash
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

重新hash操作

private void rehash() {
    // 扫描整个Entry数组并清除过时的Entry
    expungeStaleEntries();

    // Use lower threshold for doubling to avoid hysteresis
    if (size >= threshold - threshold / 4)
        // 扩容操作
        resize();
}

扫描整个Entry数组并清除过时的Entry

// 扫描整个Entry数组并清除过时的Entry
private void expungeStaleEntries() {
    Entry[] tab = table;
    int len = tab.length;
    for (int j = 0; j < len; j++) {
        Entry e = tab[j];
        // 判断是否过时
        if (e != null && e.get() == null)
            // 清除具体的过时Entry
            expungeStaleEntry(j);
    }
}

清除具体的过时Entry

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // 清除下标为 staleSlot 的 entry
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    // 循环直到遇到null
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        // 清除过时Entry
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            // 重新计算该Entry的索引位置
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) { // 如果索引位置不为当前索引位置i
                // 将i位置对象清空, 替当前Entry寻找正确的位置(当前对象已经保存在e中了)
                tab[i] = null;

                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                // 如果h位置不为null,则向h后寻找当前Entry的位置
                // **哈希碰撞**
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}

索引获取

// 获取下一个索引
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
    return ((i - 1 >= 0) ? i - 1 : len - 1);
}

扩容

private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    // 2倍容量
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;
    // 循环旧数组,处理每个节点
    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if (e != null) {
            ThreadLocal<?> k = e.get();
            // 过时Entry
            if (k == null) {
                e.value = null; // Help the GC
            } else {
                // 重新计算下标
                int h = k.threadLocalHashCode & (newLen - 1);
                // 检查碰撞,处理hash碰撞的方法就是开放地址法中的线性探测再散列
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }

    // 重新计算扩容阈值
    setThreshold(newLen);
    size = count;
    table = newTab;
}

private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

Hash冲突解决

ThreadLocalMap 没有next属性,解决方式不是使用链式结构,而是采用线性探测的方式,即根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。

创建一个 ThreadLocalMap

// 如果map为空则,创建一个ThreadLocalMap
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Thread,ThreadLocal,ThreadLocalMap 关系 6ff5dace570dba1b4c0df9674d8a3d08.jpeg

ThreadLocalMap的问题

避免泄漏 在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除

ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
try {
    threadLocal.set(new Session(1, "Misout的博客"));
    // 其它业务逻辑
} finally {
    threadLocal.remove();
}
⚠️ **GitHub.com Fallback** ⚠️