ThreadLocal家族之FastThreadLocal

186 阅读8分钟

「我正在参与掘金会员专属活动-源码共读第一期,点击参与

1.FastThreadLocal的大纲图及思路介绍

  1. 基于Netty 4.1.X版本,上述图描述的东西如下:

    • ThreadLocal 与 Netty FastThreadLocal的对比。(FastThreadLocal---空间换时间)

      • FastThreadLocal优化的方案:取号入座方案
        • 每往InternalThreadLocalMap中塞入一个新的FastThreadLocal对象,就给该FastThreadLocal对象发个唯一的下标,然后让该FastThreadLocal对象记住该下标,去InternalThreadLocalMap中找value时,直接通过该下标去取对应的value。
        • 下标为0的地方保存所有FastThreadLocal的Set。
        • 内存泄漏问题:规范使用(用完后remove掉即可)
    • InternalThreadLocalMap的核心逻辑图。

      • 整体思路(空间换时间)
        • 第一点:InternalThreadLocalMap采用Object数组来存储FastThreadLocal对象和其value。
        • 第二点:第一个位置存放了一个包含所使用的FastThreadLocal对象的set。(目的便于后面的删除工作)
        • 第三点:后面的位置存储所有的value。
          • FastThreadLocal构造时已被分配了一个唯一下标,下标对应的是value所处的下标。
    • FastThreadLocal、InternalThreadLocalMap、FastThreadLocalThread的关系。

  2. FastThreadLocal好处:

    • 提高效率的方法:
      • FastThreadLocal 通过分配下标直接定位 value ,不会有 hash 冲突。
      • FastThreadLocal 采用空间换时间。
    • 配套FastThreadLocalThread使用快速。(否则不如ThreadLocal)
    • 防止内存泄漏:FastThreadLocal 最好配套 FastThreadLocalRunnable使用,执行完任务后会主动调用 removeAll 来移除所有 FastThreadLocal。
  3. FastThreadLocal缺点:可能浪费空间。

    • 举例:一个系统中new了100个FastThreadLocal,那第100个FastThreadLocal的下标是100。

      • 如果该线程之前都没塞过FastThreadLocal,此时塞入第一个FastThreadLocal,构造出来的数组长度是32,但是该FastThreadLocal的下标已涨到了100,所以这个线程第一次塞值,数组就需扩容。
        • 白白浪费了前面的99个数组空间。
      • Netty针对该数组的扩容是基于index的(基于index的向上2次幂取整),直接进行数组的拷贝,无需进行rehash。
      • ThreadLocalMap扩容需进行rehash --- 重新基于key的hash值进行位置的设置。

2.FastThreadLocal

2.1 重要属性

    // 共同的不可变int值( variablesToRemoveIndex 的值为 0,它属于常量赋值,第一次调用时 nextIndex 的值为 0 。)
    // 静态常量是跟着类的,一个类只会有一个
    private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();

    // 当前FastThreadLocal的唯一下标:在 FastThreadLocal 构造时就被赋值
    // 常量变量是跟着对象的,每个对象的值都不一样
    private final int index;

    public FastThreadLocal() {
        // 初始化唯一下标
        index = InternalThreadLocalMap.nextVariableIndex();  
    }

    // 为了填充 Cache Line,避免伪共享问题的产生
    /** @deprecated These padding fields will be removed in the future. */
    public long rp1, rp2, rp3, rp4, rp5, rp6, rp7, rp8, rp9;
  1. variablesToRemoveIndex:静态变量,每个 FastThreadLocal 共有的值。
  2. index:给每个 FastThreadLocal 发的唯一下标。

2.2 set方法

    public final void set(V value) {
        // 如果要塞入的值不是默认值
        if (value != InternalThreadLocalMap.UNSET) {
            // 获取当前线程中的InternalThreadLocalMap
            InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
            setKnownNotUnset(threadLocalMap, value);
        } else {
            // 否则移除
            remove();
        }
    }

    private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
        if (threadLocalMap.setIndexedVariable(index, value)) {
            // 添加到待删除列表(this是FastThreadLocal对象)
            addToVariablesToRemove(threadLocalMap, this); 
        }
    }
  1. 步骤:

    • 如果要塞入的值不是默认值,则保存。
      • 获取当前线程中的InternalThreadLocalMap。
      • 保存当前值到数组的指定下标。
      • 保存当前FastThreadLocal对象到数组的下标为0的Set中。
    • 如果塞入的是默认值,则进行移除动作。

2.3 get方法

    public final V get() {
        // 获取线程中的InternalThreadLocalMap
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
        // 获取当前FastThreadLocal对应的唯一下标的值
        Object v = threadLocalMap.indexedVariable(index);
        // 值不是初始值,就直接返回
        if (v != InternalThreadLocalMap.UNSET) {
            return (V) v;
        }
        //如果是初始值,就执行初始化赋值操作
        return initialize(threadLocalMap);
    }
  1. 步骤:

    • 获取线程中的InternalThreadLocalMap。
    • 获取数组中 当前FastThreadLocal对应的唯一下标(index)的值。
    • 如果是不是初始值,就直接返回。
    • 如果是初始值,就执行初始化赋值操作。

2.4 removeAll方法

    public static void removeAll() {
        // 获取到当前线程的 InternalThreadLocalMap
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet(); 
        if (threadLocalMap == null) {
            return;
        }

        try {
            // 得到下标为0的V(获取保存所有FastThreadLocal的Set)
            Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
            if (v != null && v != InternalThreadLocalMap.UNSET) {
                @SuppressWarnings("unchecked")
                Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
                // 将Set转为数组遍历
                FastThreadLocal<?>[] variablesToRemoveArray =
                        variablesToRemove.toArray(new FastThreadLocal[0]);
                for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
                    // 调用remove方法
                    tlv.remove(threadLocalMap);
                }
            }
        } finally {
            // 将线程里面的map置空,完成整体移除
            InternalThreadLocalMap.remove();
        }
    }
  1. 步骤:

    • 获取到当前线程的 InternalThreadLocalMap。
    • 获取保存所有FastThreadLocal的Set。
    • 如果保存所有FastThreadLocal的Set 不为空的话,则循环从数组中移除每个FastThreadLocal对应值和本身。
    • 最后将线程里面的map置空。

2.5 remove方法

    public final void remove(InternalThreadLocalMap threadLocalMap) {
        if (threadLocalMap == null) {
            return;
        }
        // 移除当前FastThreadLocal的唯一下标的数据,并返回值
        Object v = threadLocalMap.removeIndexedVariable(index);
        // 将当前FastThreadLocal从数组的下标为0的Set中移除
        removeFromVariablesToRemove(threadLocalMap, this);
        // 如果移除的下标的值不是默认值的话
        if (v != InternalThreadLocalMap.UNSET) { 
            try {
                // 这是扩展,自行实现(可释放对象的内存等)
                onRemoval((V) v); 
            } catch (Exception e) {
                PlatformDependent.throwException(e);
            }
        }
    }

    // 将当前FastThreadLocal从数组的下标为0的Set中移除
    private static void removeFromVariablesToRemove(
            InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
        // 获取下标为0的Set(保存所有FastTreadLocal的Set)
        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
        // set为空或者为默认值的话,则跳过
        if (v == InternalThreadLocalMap.UNSET || v == null) { 
            return;
        }

        @SuppressWarnings("unchecked")
        Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
        // 调用set的remove
        variablesToRemove.remove(variable);
    }
  1. 步骤:

    • 移除当前FastThreadLocal的唯一下标的数据,并返回值。
    • 将当前FastThreadLocal从数组的下标为0的Set中移除。
    • 如果移除的下标的值不是默认值的话,可以自定义实现。

2.6 addToVariablesToRemove 方法:内部私有方法

    private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
        // variablesToRemoveIndex值为0(从数组的0下标处获取所有FastTreadLocal的Set)
        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
        Set<FastThreadLocal<?>> variablesToRemove;
        // 如果v 是默认值 或 v==null
        if (v == InternalThreadLocalMap.UNSET || v == null) {
            variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
            // 将该Set塞到Object数组的第0个位置
            threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
        } else {
            // 将v强转为Set
            variablesToRemove = (Set<FastThreadLocal<?>>) v;
        }
        // 将传入的FastThreadLocal塞到该set中
        variablesToRemove.add(variable);
    }
  1. 步骤:

    • 从数组的0下标处获取所有FastTreadLocal的Set。
    • 如果获取到的v为空/默认值的话,则初始化。
    • 否则将v强转为Set<FastThreadLocal<?>>。
    • 将传入的FastThreadLocal塞到这个set中。

3.InternalThreadLocalMap

3.1 重要属性

    // 保存普通非FastThreadLocalThread使用FastThreadLocal时的 InternalThreadLocalMap
    private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap =
            new ThreadLocal<InternalThreadLocalMap>();
    // 填充的默认值
    public static final Object UNSET = new Object();
    // 通过Object数组来实现
    // 数组的第一个位置(index=0)放一个 保存所有使用的 FastThreadLocal 对象的 set
    // 目的: 用于删除
    private Object[] indexedVariables; 

    private InternalThreadLocalMap() {
        indexedVariables = newIndexedVariableTable();
    }
    // 构造默认生成32长度的数组,并填充默认值
    private static Object[] newIndexedVariableTable() {
        Object[] array = new Object[INDEXED_VARIABLE_TABLE_INITIAL_SIZE];
        Arrays.fill(array, UNSET);
        return array;
    }
  1. nextIndex:发号器(通过AtomicInteger实现),为FastThreadLocal的 variablesToRemoveIndex、index属性生成值。

  2. indexedVariables:通过Object数组来实现。

    • 数组的第一个位置(index=0):保存所有使用的 FastThreadLocal 对象的 set 。

      • 目的: 便于删除数据。
    • 数组其它位置保存真实值。

  3. slowThreadLocalMap:保存普通非FastThreadLocalThread使用FastThreadLocal时的 InternalThreadLocalMap。

3.2 setIndexedVariable方法

    public boolean setIndexedVariable(int index, Object value) {
        // 保存数据的Object数组
        Object[] lookup = indexedVariables; 
        // 重点:默认数组是32,如果index大于数组长度,则需扩容。
        // 如果第一个塞入的值的线程的index为100,则仅只有该一个值,数组就需扩容
        if (index < lookup.length) { 
            // 直接通过索引找到位置进行替换
            Object oldValue = lookup[index];
            lookup[index] = value;
            return oldValue == UNSET;
        } else {
            // 进行扩容
            expandIndexedVariableTableAndSet(index, value);
            return true;
        }
    }
  1. 步骤:

    • 判断 index 是否大于数组长度。
    • 如果index大于数组长度,则需扩容。
    • 否则直接通过index找到位置进行替换即可。
  2. 缺点:如果第一个塞入的值的线程的index为100,则仅仅只有这么一个值,数组就需扩容。

3.3 removeIndexedVariable方法

    // 把对应下标的值设置为默认值,然后返回原值
    public Object removeIndexedVariable(int index) { 
        Object[] lookup = indexedVariables;
        if (index < lookup.length) {
            Object v = lookup[index];
            // 找到位置,覆盖成初始化值
            lookup[index] = UNSET;
            return v;
        } else {
            return UNSET;
        }
    }
  1. 步骤:

    • 如果下标小于数组长度,则找到位置,使用默认值覆盖,然后返回旧值。
    • 如果下标不小于数组长度,则直接返回默认值。

3.4 expandIndexedVariableTableAndSet方法:扩容方法

    // 数据扩容是基于index而不是原先数组的大小
    private void expandIndexedVariableTableAndSet(int index, Object value) {
        Object[] oldArray = indexedVariables;
        final int oldCapacity = oldArray.length;
        // 基于index的向上2次幂取整(2次幂取整跟HashMap类似)
        int newCapacity = index;
        newCapacity |= newCapacity >>>  1;
        newCapacity |= newCapacity >>>  2;
        newCapacity |= newCapacity >>>  4;
        newCapacity |= newCapacity >>>  8;
        newCapacity |= newCapacity >>> 16;
        newCapacity ++;
        // 直接数组拷贝,无需rehash(优于ThreadLocalMap的地方,ThreadLocalMap需rehash,重新基于Key的hash值进行位置的分配)
        Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
        Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
        // 设置目标下标的值
        newArray[index] = value;
        indexedVariables = newArray;
    }
  1. 步骤:

    • 基于index的向上2次幂取整(2次幂取整跟HashMap类似)
    • 直接数组拷贝。(无需rehash,因为FastThreadLocal的index都是固定的)
    • 设置目标下标的值。

3.5 indexedVariable方法:获取某个下标(某个FastThreadLocal)的数据

    // 直接进行一个数组的下标获取
    public Object indexedVariable(int index) {
        Object[] lookup = indexedVariables;
        return index < lookup.length? lookup[index] : UNSET;
    }
  1. 步骤:直接进行一个数组的下标获取。

3.6 get方法

    // 获取当前线程的InternalThreadLocalMap
    public static InternalThreadLocalMap get() { 
        // 获取当前线程
        Thread thread = Thread.currentThread();
        // FastThreadLocalThread相关的类的话,则返回FastThreadLocalThread下的threadLocalMap字段
        if (thread instanceof FastThreadLocalThread) {
            // 如果当前线程是fastThread类型,就执行fastGet
            return fastGet((FastThreadLocalThread) thread);
        } else {
            // 否则就slowGet
            return slowGet(); 
        }
    }

    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
        // 获取InternalThreadLocalMap
        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
        //为空就new一个
        if (threadLocalMap == null) {
            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
        }
        // 直接返回
        return threadLocalMap;
    }
    // 从当前线程 中的 threadLocals字段下,通过ThreadLocal获取对应的 InternalThreadLocalMap
    private static InternalThreadLocalMap slowGet() { 
        InternalThreadLocalMap ret = slowThreadLocalMap.get();
        if (ret == null) {
            // new一个新的InternalThreadLocalMap放入到当前线程的threadLocals变量中
            ret = new InternalThreadLocalMap();
            slowThreadLocalMap.set(ret);
        }
        return ret;
    }
  1. 作用:获取当前线程的InternalThreadLocalMap

  2. 步骤:

    • 获取当前线程。
    • 如果是FastThreadLocalThread相关的类的话,则返回FastThreadLocalThread下的threadLocalMap字段。
    • 如果不是FastThreadLocalThread相关的类的话,从当前线程 中的 threadLocals字段下,通过ThreadLocal获取对应的 InternalThreadLocalMap。

4.FastThreadLocalThread

4.1 重要属性

    // 当前线程保存的变量(通过FastThreadLocal查询)
    private InternalThreadLocalMap threadLocalMap; 
  1. threadLocalMap: 当前线程保存的变量(通过FastThreadLocal查询)

4.2 构造方法

    public FastThreadLocalThread(Runnable target) {
        // 包装成FastThreadLocalRunnable
        super(FastThreadLocalRunnable.wrap(target)); 
        cleanupFastThreadLocals = true;
    }
  1. 将Runnbale包装为FastThreadLocalRunnable。

5.FastThreadLocalRunnable

5.1 重要属性

    // 被包装的Runnable
    private final Runnable runnable;
  1. runnable:被包装的真实的任务。

5.2 run方法

    @Override
    public void run() {
        try {
            runnable.run();
        } finally {
            //  Runnable 执行完毕之后,会主动调用 FastThreadLocal.removeAll() 来清理所有的 FastThreadLocal
            FastThreadLocal.removeAll();
        }
    }
  1. Runnable 执行完毕后,主动清理所有的 FastThreadLocal。

5.3 包装情况

    static Runnable wrap(Runnable runnable) {
        return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
    }
  1. 将Runnable类包装成FastThreadLocalRunnable。

6.ThreadLocal与FastThreadLocal的性能对比情况

  1. 普通Thread+ThreadLocal/FastThreadLocal对比:

    • 代码:io.netty.microbench.concurrent.FastThreadLocalSlowPathBenchmark

    • 结果:

  • 建议:使用普通Thread的情况下,建议使用ThreadLocal即可。
  1. FastThreadLocalThread+ThreadLocal/FastThreadLocal对比:

    • 代码:io.netty.microbench.concurrent.FastThreadLocalFastPathBenchmark
    • 结果:

  • 建议:使用FastThreadLocalThread的情况下,建议使用FastThreadLocal即可。

7.Dubbo对好代码的"Ctrl+C/V "

  1. dubbo的org.apache.dubbo.common.threadlocal包下的InternalThreadLocal、InternalThreadLocalMap、InternalThread类 是分别参照 Netty的FastThreadLocal、InternalThreadLocalMap、FastThreadLocalThread类进行编写的。(此处吐槽下,好的代码我们直接Ctrl+C、Ctrl+V 哈哈哈哈)