FastThreadLocal原理

ThreadLocal

ThreadLocal有一个ThreadLocalMap内部类,数据都是存储在ThreadLocalMap中。Thread类里面有一个ThreadLocal.ThreadLocalMap的变量threadLocals

每个线程的本地变量并不是存放在ThreadLocal里面,而是存放在调用线程的threadLocals变量里,也就是说ThreadLocal类型的本地变量存放在具体的线程内存空间中, ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程的threadLocals里面并存放起来,当调用线程调用它的get时,再从线程的threadLocals里面将其拿出来使用。

ThreadLocalMap,它是一种使用线性探测法实现的哈希表,底层使用数组存储数据,可以理解成就是一个HashMap,这个哈希表的keyThreadLocalvalueThreadLocal存储的值。

使用线性探测法的哈希表使用数组存储元素,每次添加元素的时候,如果hash到的位置已经有元素了,则向后移动一位检测是否有元素,如果还有元素,继续往后移直到一个没有元素的位置把当前要添加的元素放在那个位置。


比如,对于一个容量为8的哈希表,我们依次放入2、10、3、4、11这么几个元素:

hash值为2,2%8=2,所以放在下标为2的位置

下标01234567
哈希2

hash值为10,10%8=2,所以放在下标为 2 的位置,但是下标2的位置有元素了,往后移一位,到下标为3的位置,下标为3没有元素,所以10放在了下标为3的位置;

下标01234567
哈希210

hash值为3,3%8=3,所以放在下标为3的位置,但是下标3的位置有元素了,往后移一位,到下标为4的位置,没有元素,所以3放在了下标为4的位置;

下标01234567
哈希2103

hash值为4,4%8=4,所以放在下标为4的位置,但是下标4的位置有元素了,往后移一位,到下标为5的位置,没有元素,所以4放在了下标为5的位置;

下标01234567
哈希21034

hash值为11,11%8=3,所以放在下标为3的位置,但是下标3的位置有元素了,往后移一位,到下标为4的位置,也有元素了,继续后移,到下标为5的位置,依然有元素,继续后移,到下标为6的位置,没有元素,所以11放在了下标为6的位置

下标01234567
哈希2103411

使用线性探测法实现的哈希表非常容易出现哈希冲突,且解决冲突的过程时间复杂度非常高,最高可以达到O(n)的时间复杂度。

ThreadLocalMap中的Entry是一个弱引用,它引用的对象就是它的key值,即ThreadLocal变量,所以,当这个key不具有强引用时,下一次垃圾回收时,这个弱引用本身会进入到引用队列中,等待被回收。不过我们一般把ThreadLocal作为类的静态私有变量来使用。

缺点:

  • 存储数据的ThreadLocalMap使用的是线性探测法,效率低下
  • 使用结束未及时remove()ThreadLocal中的值,容易造成内存泄漏,这种情况只能依靠下一次调用ThreadLocal的时候来清除无用的数据,可以参考ThreadLocalset()/get()/remove()中的相关代码

FastThreadLocal

学习FastThreadLocal原理,还需要知道另外两个类:InternalThreadLocalMapFastThreadLocalThread

InternalThreadLocalMap:对原生ThreadLocalMap的优化,它不再使用线性探测法实现,查看FastThreadLocal无参构造函数

public class FastThreadLocal<V> {
    // 下标为0的被这个静态常量占用了
    private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
    private final int index;
    public FastThreadLocal() {
        index = InternalThreadLocalMap.nextVariableIndex();
    }
}

每个FastThreadLocal创建时都会给它分配一个index,使用这个index定位FastThreadLocal在数组中的精确位置,可以让时间复杂度降低到O (1),这个性能提升是很明显的。

比如,我们放入2、10、3、4、11这么几个元素:每个元素创建的时候都给它分配一个index,对于2、10、3、4、11,它们的索引分别是1、2、3、4、5,这样,它们在放入数组的时候直接按这个index作为下标去数组对应的位置即可,也不会出现hash冲突,当索引大小达到了数组长度扩容就好了。

这样使用也有个问题,当一个元素被回收的时候,它的index并不会回收,也就是数组对应的位置不能重复利用,但是,这也不是一个问题,因为FastThreadLocal一般作为类的静态属性来使用,正常情况下,是永远不会被回收的,所以,在FastThreadLocal的使用场景下,这样的Map完全没有问题。

InternalThreadLocalMap内部使用数组存储FastThreadLocal

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
    // 数组默认值都为这个UNSET
    public static final Object UNSET = new Object();
    // 存储FastThreadLocal的数组
    private Object[] indexedVariables;

    private InternalThreadLocalMap() {
        indexedVariables = newIndexedVariableTable();
    }

    private static Object[] newIndexedVariableTable() {
        Object[] array = new Object[INDEXED_VARIABLE_TABLE_INITIAL_SIZE];
        Arrays.fill(array, UNSET);
        return array;
    }
}

FastThreadLocalThread:对Thread的包装,目的是为了配合FastThreadLocal的使用。里面封装了一个InternalThreadLocalMap字段,这样的话,以前使用ThreadthreadLocals存储元素就变成了使用这个InternalThreadLocalMap来存储元素。FastThreadLocalThread中的构造方法还对任务进行了包装,使得线程任务运行结束的时候可以自动清理掉本线程相关的本地变量。

使用案例

public class FastThreadLocalTest {

    private static FastThreadLocal<String> USER_THREAD_LOCAL = new FastThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        // thread1
        new FastThreadLocalThread(() -> {
            USER_THREAD_LOCAL.set("test001");
            System.out.println("001:" + USER_THREAD_LOCAL.get());
        }, "thread-001").start();
        
        Thread.sleep(2000);
    }
}

源码分析

FastThreadLocal

查看FastThreadLocal构造函数

public class FastThreadLocal<V> {
    // 下标为0的被这个静态常量占用了
    private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
    private final int index;
    public FastThreadLocal() {
        index = InternalThreadLocalMap.nextVariableIndex();
    }
}

InternalThreadLocalMap.nextVariableIndex()

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
    public static int nextVariableIndex() {
      	// nextIndex 是一个 AtomicInteger 类型
        int index = nextIndex.getAndIncrement();
        if (index < 0) {
            nextIndex.decrementAndGet();
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }
}

FastThreadLocal创建的时候给它分配了一个index,这个index是从一个AtomicInteger中获取的,理论上来说,index的值应该从0开始,但是FastThreadLocal中有一个常量字段variablesToRemoveIndex,它会在FastThreadLocal加载的时候就从 AtomicInteger中拿走了第一个值0,所以,实际上,FastThreadLocal中的index是从1开始的。

接着看FastThreadLocalThread构造函数

public class FastThreadLocalThread extends Thread {
    public FastThreadLocalThread(Runnable target, String name) {
        // FastThreadLocalRunnable.wrap(target)包装任务
        // 然后调用父类
        super(FastThreadLocalRunnable.wrap(target), name);
        cleanupFastThreadLocals = true;
    }
}

看看FastThreadLocalRunnable是干啥的

final class FastThreadLocalRunnable implements Runnable {
    // 原始任务
    private final Runnable runnable;

    private FastThreadLocalRunnable(Runnable runnable) {
        this.runnable = ObjectUtil.checkNotNull(runnable, "runnable");
    }

    @Override
    public void run() {
        try {
            // 1. 运行原始任务
            runnable.run();
        } finally {
            // 2. run()结束后清理掉FastThreadLocal
            FastThreadLocal.removeAll();
        }
    }
    // 包装方法
    static Runnable wrap(Runnable runnable) {
        return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable);
    }
}

FastThreadLocal#set()

FastThreadLocalset方法

public class FastThreadLocal<V> {
    public final void set(V value) {
        // 如果value不等于UNSET,就把它set进去
        // 初始化时InternalThreadLocalMap中的数组的每个元素都会被初始化为UNSET
        if (value != InternalThreadLocalMap.UNSET) {
            // ①获取存储元素的InternalThreadLocalMap
            InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
            // ②设置值
            setKnownNotUnset(threadLocalMap, value);
        } else {
            remove();
        }
    }
}

InternalThreadLocalMap.get()

InternalThreadLocalMap.get()

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
    public static InternalThreadLocalMap get() {
        Thread thread = Thread.currentThread();
        // 判断当前线程是不是FastThreadLocalThread
        // 这是为了兼容非FastThreadLocalThread也能使用FastThreadLocal
        if (thread instanceof FastThreadLocalThread) {
            return fastGet((FastThreadLocalThread) thread);
        } else {
            return slowGet();
        }
    }
    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
        // 这个Map是存储在FastThreadLocalThread中的, 变量名为threadLocalMap
        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
        if (threadLocalMap == null) {
            // 初始化Map
            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
        }
        return threadLocalMap;
    }

    private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap =
                new ThreadLocal<InternalThreadLocalMap>();

    private static InternalThreadLocalMap slowGet() {
        // 使用Java原生的ThreadLocal来存储一个InternalThreadLocalMap
        // 跟Netty相关的本地变量还是存储在InternalThreadLocalMap中
        InternalThreadLocalMap ret = slowThreadLocalMap.get();
        if (ret == null) {
            ret = new InternalThreadLocalMap();
            slowThreadLocalMap.set(ret);
        }
        return ret;
    }
}

获取存储本地线程变量的InternalThreadLocalMap分为两种情况:

  1. 如果当前线程是FastThreadLocalThread,则直接取FastThreadLocalThreadthreadLocalMap属性
  2. 如果当前线程不是FastThreadLocalThread,则从FastThreadLocalThreadslowThreadLocalMap中获取,slowThreadLocalMap是一个ThreadLocal,在这个ThreadLocal里面保存了一个线程本地变量InternalThreadLocalMap

setKnownNotUnset

拿到InternalThreadLocalMap后,看看是怎么把值设置进去的。

public class FastThreadLocal<V> {
    private final int index;
    public FastThreadLocal() {
        index = InternalThreadLocalMap.nextVariableIndex();
    }
    private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
        // 设置值到InternalThreadLocalMap中
        if (threadLocalMap.setIndexedVariable(index, value)) {
            // 将当前FastThreadLocal添加到待清理的Set中
            addToVariablesToRemove(threadLocalMap, this);
        }
    }
}

threadLocalMap.setIndexedVariable(index, value)

设置值到InternalThreadLocalMap

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
    public boolean setIndexedVariable(int index, Object value) {
        // 存储FastThreadLocal元素的数组
        Object[] lookup = indexedVariables;
        if (index < lookup.length) {
            // 直接通过索引下标要存储的位置, 时间复杂度为O(1)
            Object oldValue = lookup[index];
            lookup[index] = value;
            return oldValue == UNSET;
        } else {
            // 容量不够 需要先扩容再设置值
            expandIndexedVariableTableAndSet(index, value);
            return true;
        }
    }
    private void expandIndexedVariableTableAndSet(int index, Object value) {
        // 旧数组
        Object[] oldArray = indexedVariables;
        // 旧元素
        final int oldCapacity = oldArray.length;
        // 找到大于index的最小的2次方
        // 比如,index=3,则为4
        // index=16,则为32
        // 如何实现取大于等于自己的最小2次方呢?只需要把这里改成(index-1)即可
        int newCapacity = index;
        newCapacity |= newCapacity >>>  1;
        newCapacity |= newCapacity >>>  2;
        newCapacity |= newCapacity >>>  4;
        newCapacity |= newCapacity >>>  8;
        newCapacity |= newCapacity >>> 16;
        newCapacity ++;
        // 创建新数组,并把旧数组的元素全部拷贝过来
        Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
        // 把新数组不包含旧数组的后面部分都初始化为UNSET
        Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
        // 设置当前的值
        newArray[index] = value;
        // 把新数组赋值给InternalThreadLocalMap存储元素的数组
        indexedVariables = newArray;
    }
}

设置值分两种情况:

  1. 容量足够,直接把数组对应下标位置的值设置成新值即可,时间复杂度为O(1)

  2. 容量不够,先扩容,再设置新值

为什么使用index作为基准来扩容,而不是直接扩容为旧数组容量的2倍?

比如默认这个数组的容量是32,假设我们有100FastThreadLocal变量,且它们都没有set()过任何值,
此时,这个数组大小依然是32,现在如果要设置第100FastThreadLocal的值,根据前面的逻辑,我们知道,它的index100
那么此时,如果按旧数组的容量扩容为2倍,依然无法承载index100的这个元素,所以,需要按index作为基准来进行扩容。


addToVariablesToRemove

此时数据已经设置到InternalThreadLocalMap中,在上面,在添加成功之后,还有一步操作是把当前FastThreadLocal添加到待清理的Set中

public class FastThreadLocal<V> {
    private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
    private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
        // variablesToRemoveIndex是FastThreadLocal中的常量,它的值为0
        // 它对应的值同样存储在InternalThreadLocalMap中,是下标为0的元素
        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
        // InternalThreadLocalMap里面的0号元素是一个Set
        Set<FastThreadLocal<?>> variablesToRemove;
        if (v == InternalThreadLocalMap.UNSET || v == null) {
            // 初始化这个Set
            variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
            // 设置值到InternalThreadLocalMap中
            threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
        } else {
            variablesToRemove = (Set<FastThreadLocal<?>>) v;
        }
        // 把当前FastThreadLocal添加到这个Set中
        variablesToRemove.add(variable);
    }
}

FastThreadLocalRunnable

final class FastThreadLocalRunnable implements Runnable {
    @Override
    public void run() {
        try {
            // 1. 运行原始任务
            runnable.run();
        } finally {
            // 2. run()结束后清理掉FastThreadLocal
            FastThreadLocal.removeAll();
        }
    }
}

Runnable任务运行完毕后,里面会执行清理工作。

public class FastThreadLocal<V> {
    private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
    public static void removeAll() {
        // 获取 InternalThreadLocalMap
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
        if (threadLocalMap == null) {
            return;
        }

        try {
            // 取出待清理的FastThreadLocal
            Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
            if (v != null && v != InternalThreadLocalMap.UNSET) {
                // 转换为Set
                @SuppressWarnings("unchecked")
                Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
                // Set转数组
                FastThreadLocal<?>[] variablesToRemoveArray =
                        variablesToRemove.toArray(new FastThreadLocal[0]);
                // 遍历数组
                for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
                    // ①调用FastThreadLocal的remove()方法
                    tlv.remove(threadLocalMap);
                }
            }
        } finally {
            // ②调用InternalThreadLocalMap的remove()方法
            InternalThreadLocalMap.remove();
        }
    }
}

这里主要做了两件事:

  1. 遍历当前线程中所有的FastThreadLocal并调用remove方法
  2. 最后会调用InternalThreadLocalMapremove()方法

public class FastThreadLocal<V> {
    public final void remove(InternalThreadLocalMap threadLocalMap) {
        if (threadLocalMap == null) {
            return;
        }
        // 从InternalThreadLocalMap中移除,这里是重置为UNSET元素
        Object v = threadLocalMap.removeIndexedVariable(index);
        // 从待清理的Set中移除
        removeFromVariablesToRemove(threadLocalMap, this);
        
        if (v != InternalThreadLocalMap.UNSET) {
            try {
                // 执行钩子方法, 是一个空方法
                // Netty为我们预留的一个方法,我们可以继承自FastThreadLocal并实现onRemoval()方法
                // 在移除FastThreadLocal的时候做一些我们自己的逻辑,比如清理我们自定义的资源等。
                onRemoval((V) v);
            } catch (Exception e) {
                PlatformDependent.throwException(e);
            }
        }
    }
}

public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
    public static void remove() {
        Thread thread = Thread.currentThread();
        if (thread instanceof FastThreadLocalThread) {
            // 从FastThreadLocalThread中移除InternalThreadLocalMap
            ((FastThreadLocalThread) thread).setThreadLocalMap(null);
        } else {
            // 从ThreadLocal中移除InternalThreadLocalMap本身
            slowThreadLocalMap.remove();
        }
    }
}

总结

  1. FastThreadLocal正常来说是跟FastThreadLocalThread联合使用的。但是,Netty 为了兼容性,也可以跟普通的Thread一起使用,只是会使用一种slow的方式来运行,这个slow主要体现在InternalThreadLocalMap的存储上,使用 FastThreadLocalThread时,它是存储在FastThreadLocalThread中的,使用普通的Thread时,它是存储在Java原生的 ThreadLocal中的。
  2. FastThreadLocal能帮助我们自动清理相关资源,这是通过包装Runnable来实现的。

Netty版本

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.63.Final</version>
</dependency>
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值