美文网首页
ThreadLocal与FastThreadLocal

ThreadLocal与FastThreadLocal

作者: 书唐瑞 | 来源:发表于2020-11-01 03:19 被阅读0次
    java.lang.ThreadLocal
    

    ThreadLocal类通过线程封闭的方式解决线程安全,提到它,大家都会想到弱引用和内存泄漏等话题,它的get/set/remove等方法,网上有很多关于它的话题和详细介绍.这篇文章不会介绍这些内容.

    ThreadLocal作为key被存放到线程的ThreadLocal.ThreadLocalMap中.我们简单看下它的set方法.

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // this就是ThreadLocal,它作为key
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    private void set(ThreadLocal<?> key, Object value) {
    
        Entry[] tab = table;
        int len = tab.length;
        // 这里的key就是ThreadLocal对象,根据它的threadLocalHashCode属性值进行取模计算,得到它应该所在的下标值是多少
        int i = key.threadLocalHashCode & (len-1);
    
    
        for (Entry e = tab[i];
             e != null;
             // 如果所在的下标已经有值了,则根据线性探测继续计算下一个下标值
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
    
            ...
        }
        ...
    }
    
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }
    

    根据以上的分析,ThreadLocal对象作为ThreadLocal.ThreadLocalMap中的key存放在Map中.(Map的底层是基于数组)
    具体是根据ThreadLocal对象的threadLocalHashCode属性值进行取模计算出它在数组中的下标,假如ThreadLocal的threadLocalHashCode=1253254570,数组的默认长度是16,因此threadLocalHashCode & (16-1)=10,因此这个ThreadLocal对象应该放在数组的第10个位置.如果第10个位置已经有别的ThreadLocal对象霸占了,那么它就会尝试第11个位置是否有空缺,以此类推,直到找到一个空缺位置.

    开放定址法中的线性探测

    举例如下

    import io.netty.util.concurrent.FastThreadLocal;
    
    public class Address {
        // 定义了两个ThreadLocal对象
        public static final ThreadLocal<String> COMPANY = new ThreadLocal<String>() {
            @Override
            protected String initialValue() {
                return "CHINA";
            }
        };
    
        public static final ThreadLocal<Integer> YEAR = new ThreadLocal<Integer>() {
            @Override
            protected Integer initialValue() {
                return 1949;
            }
        };
    }
    
    Thread t1 = new Thread(() -> {
        System.out.println(Address.COMPANY.get());
        System.out.println(Address.YEAR.get());
        try {
            TimeUnit.MINUTES.sleep(15);// 只是为了让线程不终止
        } catch (InterruptedException ignored) { }
    }, "thread-1");
    t1.start();
    
    Thread t2 = new Thread(() -> {
        System.out.println(Address.COMPANY.get());
        System.out.println(Address.YEAR.get());
        try {
            TimeUnit.MINUTES.sleep(15);// 只是为了让线程不终止
        } catch (InterruptedException ignored) { }
    }, "thread-2");
    t2.start();
    
    image.png image.png image.png image.png

    根据代码和截图内容,我们总结下
    线程2的哈希值1253254570的ThreadLocal存在索引10位置
    线程2的哈希值-1401181199的ThreadLocal存在索引1位置
    线程1的哈希值1253254570的ThreadLocal存在索引10位置
    线程1的哈希值-1401181199的ThreadLocal存在索引1位置

    JDK的ThreadLocal是根据它的哈希值然后再取模计算出索引位置,如果冲突还要再根据开放地址法-线性探测继续寻找下一个可用索引的位置.性能是比较低的. 那么我们看下在Netty中它是如何优化这个功能的呢?

    import io.netty.util.concurrent.FastThreadLocalThread;
    
    
    FastThreadLocalThread t3 = new FastThreadLocalThread(() -> {
    
        System.out.println(Address.FAST_COMPANY.get());
        System.out.println(Address.FAST_YEAR.get());
    
        try {
            TimeUnit.MINUTES.sleep(15);
        } catch (InterruptedException ignored) {
    
        }
    
    }, "thread-3");
    t3.start();
    
    FastThreadLocalThread t4 = new FastThreadLocalThread(() -> {
    
        System.out.println(Address.FAST_COMPANY.get());
        System.out.println(Address.FAST_YEAR.get());
    
        try {
            TimeUnit.MINUTES.sleep(15);
        } catch (InterruptedException ignored) {
    
        }
    
    }, "thread-4");
    t4.start();
    

    这段代码使用了Netty提供的FastThreadLocalThread线程,在它的内部有个自己的InternalThreadLocalMap,这个Map是与FastThreadLocal搭配使用的.

    FastThreadLocal内部并不像JDK的ThreadLocal是根据哈希值与取模计算索引位置,它是在创建FastThreadLocal的时候就已经确定了索引位置,在JVM中每个FastThreadLocal的索引值都是不同的.
    根据索引直接存取值,时间复杂度O(1)

    private final int index;
    public FastThreadLocal() {
        // 创建FastThreadLocal时它的索引值index就确定下来了
        index = InternalThreadLocalMap.nextVariableIndex();
    }
    
    image.png

    像Netty这样追求性能的底层网络框架,自己设计一个FastThreadLocalThread线程类,自己设计一个FastThreadLocal类,自己设计一个InternalThreadLocalMap类等.

    公众号: Netty历险记

    相关文章

      网友评论

          本文标题:ThreadLocal与FastThreadLocal

          本文链接:https://www.haomeiwen.com/subject/ipfyvktx.html