美文网首页
Netty_ThreadLocal和FastThreadLoca

Netty_ThreadLocal和FastThreadLoca

作者: wo883721 | 来源:发表于2021-11-27 18:10 被阅读0次

    在平常开发的时候,经常使用到线程本地变量,这种类型的变量会在每个线程中都有一份,互相不会产生影响,这样来解决多线程并发问题。
    那么是如何实现的呢?

    一. ThreadLocal<T>

    1.1 例子

       private static final ThreadLocal<AtomicInteger> threadLocal = new ThreadLocal<AtomicInteger>(){
            @Override
            protected AtomicInteger initialValue() {
                AtomicInteger result = new AtomicInteger(0);
                System.out.println("创建 AtomicInteger("+result.hashCode()+")   thread:"+Thread.currentThread().getName());
                return result;
            }
        };
    
        public static void main(String[] args) {
    
            for (int index = 0; index < 2; index++) {
                new Thread(() -> {
                   for (int i = 0; i < 5; i++) {
                       System.out.println(threadLocal.get().incrementAndGet()
                               +"   thread:"+Thread.currentThread().getName());
                   }
                }).start();
            }
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    

    运行结果

    创建 AtomicInteger(952204834)   thread:Thread-0
    创建 AtomicInteger(471787139)   thread:Thread-1
    1   thread:Thread-0
    2   thread:Thread-0
    3   thread:Thread-0
    4   thread:Thread-0
    1   thread:Thread-1
    5   thread:Thread-0
    2   thread:Thread-1
    3   thread:Thread-1
    4   thread:Thread-1
    5   thread:Thread-1
    

    从运行结果可以得出每个线程都创建了AtomicInteger 实例,因此彼此不会产生影响。

    ThreadLocal<T> 可以看出两部分:

    • 一个是ThreadLocal 对象实例(即例子中的 threadLocal),这个实例只有一个,多线程共享的。
    • 另一个是由ThreadLocal 对象实例创建的对象(即例子中的AtomicInteger),这个是每个线程都会创建并持有。

    因此你会发现:

    • 每个线程可以根据ThreadLocal 对象实例threadLocal来查找对应的所创建的对象AtomicInteger,相当于key->value 的键值映射关系。
    • 而每个线程可以有多个ThreadLocal 对象实例,即多个key
    • 那么我们可以断定,每个线程肯定有一个集合对象来存储上面的多个key->value 键值映射关系,其实就是 Thread 中成员属性 ThreadLocal.ThreadLocalMap threadLocals

    1.2 get 方法

        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
    
        public T get() {
            // 先获取当前线程
            Thread t = Thread.currentThread();
            // 从当前线程中获取存储键值映射关系的Map
            ThreadLocal.ThreadLocalMap map = getMap(t);
            if (map != null) {
                // 如果这个Map存在,那么直接从里面获取映射关系e
                ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    // 映射关系e 存在,那么直接获取创建的对象
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            // 程序走到这里,说明当前线程 这个ThreadLocal 实例对应对象还没有创建,
            // 那么就进行初始化创建
            return setInitialValue();
        }
    

    从当前线程存储的映射关系集合 threadLocals 中,查找当前这个ThreadLocal 对象实例所对应的对象是否存在;存在就返回,不存在就setInitialValue() 方法进行创建。

    1.3 setInitialValue() 方法

        private T setInitialValue() {
            // 调用 initialValue() 方法得到初始化值
            T value = initialValue();
            // 先获取当前线程
            Thread t = Thread.currentThread();
            // 从当前线程中获取存储键值映射关系的Map
            ThreadLocal.ThreadLocalMap map = getMap(t);
            if (map != null)
                // 存储 key-value 的映射关系
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }
    
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    

    1.4 ThreadLocalMap

    ThreadLocalMap也是用一个哈希表数据结构来储存key-value 的映射关系,只不过它不是用链地址法来解决哈希冲突,而是用开放地址法的 线性探测来解决哈希冲突。

    关于哈希表,以及链地址法和开放地址法的原理,在我的这篇文章哈希表 中有全面的介绍。

    1.5 小结

    ThreadLocal.png

    从图中我们就可以知道,每个线程中都一个threadLocals 属性,它的类型是 ThreadLocalMap, 这个 ThreadLocalMap 会记录当前线程所有产生的 ThreadLocal 对象。

    二. FastThreadLocal<V>

     private static final FastThreadLocal<AtomicInteger> fastThreadLocal = new FastThreadLocal<AtomicInteger>(){
            @Override
            protected AtomicInteger initialValue() {
                AtomicInteger result = new AtomicInteger(0);
                System.out.println("创建 AtomicInteger("+result.hashCode()+")   thread:"+Thread.currentThread().getName());
                return result;
            }
        };
    
        public static void main(String[] args) {
            for (int index = 0; index < 2; index++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < 5; i++) {
                            System.out.println(fastThreadLocal.get().incrementAndGet()
                                    +"   thread:"+Thread.currentThread().getName());
                        }
                    }
                }).start();
            }
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
    

    运行结果

    创建 AtomicInteger(125165235)   thread:Thread-1
    创建 AtomicInteger(1223947416)   thread:Thread-0
    1   thread:Thread-1
    1   thread:Thread-0
    2   thread:Thread-1
    2   thread:Thread-0
    3   thread:Thread-1
    3   thread:Thread-0
    4   thread:Thread-1
    5   thread:Thread-1
    4   thread:Thread-0
    5   thread:Thread-0
    

    从运行结果来看,FastThreadLocal<V>ThreadLocal<T> 效果是一样的,那么 FastThreadLocal<V> 的优点在哪里呢?

    从上面的介绍中,我们知道 ThreadLocal<T> 通过哈希表来储存数据,从哈希表查找数据的过程如下:

    • 根据 ThreadLocal<T> 实例对象threadLocal的哈希值,得到对应数组下标。
    • 再比较这个数组下标存储的映射关系entrykey 和实例对象threadLocal是否相等,相等的话,就直接返回entryvalue值。
    • 如果不等,那么就要进行线性探测,查找下一个下标的映射关系entry,是否符合要求,知道找到映射关系entrykey和当前实例对象threadLocal相等。
    • 所以对于这种开发地址法的哈希表,极个别情况下,查找过程可能会耗时,要进行多次线性探测。

    那么 FastThreadLocal<V> 就采用了空间换时间的方式加快查找速度。

    • ThreadLocal<T> VS FastThreadLocal<V>
      • ThreadLocal<T> 有一个threadLocalHashCode 属性,在创建的时候被赋值,而且是不可变的属性,代表当前这个 ThreadLocal<T>实例对象的哈希值,用来在哈希表ThreadLocalMap中查找对应的映射关系。
      • FastThreadLocal<V> 有一个 index 属性,在创建的时候被赋值,而且是不可变的属性,这个值就代表当前FastThreadLocal<V>实例对象在 InternalThreadLocalMap 实例的 indexedVariables 的下标,通过这个下标得到FastThreadLocal<V>所创建的当前线程对象。
    • ThreadLocalMapInternalThreadLocalMap
      • ThreadLocalMap 是一个哈希表,用来储存ThreadLocal<T>实例对象和它所创建的当前线程对象的映射关系,就可以通过ThreadLocal<T>实例对象查找它所创建的当前线程对象。
      • InternalThreadLocalMap 就是一个数组,用来存储FastThreadLocal<V>实例对象所创建的当前线程对象,不过存储这个值的数组下标就是FastThreadLocal<V>实例对象的index 属性值。
      • InternalThreadLocalMap 数组下标0 这个位置比较特殊,0 下标存储当前线程所有的 FastThreadLocal<V> 对象实例,用于当前线程销毁时,移除当前线程所有的 FastThreadLocal<V> 对象实例所创建的当前线程对象。
    FastThreadLocal.png

    需要注意的点:

    • 每个FastThreadLocal 再创建的时候,index 属性就被赋值了,也就是说这个 FastThreadLocal 实例,在每个线程获取的 InternalThreadLocalMap 中的下标是一样的,都是 index

      这就导致一个严重问题,如果 FastThreadLocal 实例较多的话,某一个线程用到了 index 较大FastThreadLocal 实例的话,它必须创建一个很大的数组,这样才能在FastThreadLocal 实例对应下标 index 中储存FastThreadLocal 实例创建的对象。

    • ThreadLocal 没有这个问题,虽然它的哈希值也是创建的时候就确定了,但是它通过 哈希的方法寻找数组下标,那么当前线程中ThreadLocalMap 的数组长度只会和当前线程拥有的ThreadLocal 实例有关。

      这个的问题就是通过哈希查找,效率有点影响。

    • InternalThreadLocalMap0 下标做了特殊处理,用来存放每个线程拥有的 FastThreadLocal 实例集合,当线程退出时,清理这些 FastThreadLocal 实例为当前线程中产生的对象。
    • ThreadLocalMap 没有做这方面的处理,那是因为 ThreadLocalMap 中使用 WeakReference<ThreadLocal<?>> 来记录 ThreadLocal<?> 实例的。

    相关文章

      网友评论

          本文标题:Netty_ThreadLocal和FastThreadLoca

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