Netty_ThreadLocal和FastThreadLoca

2021-11-27  本文已影响0人  wo883721

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

一. 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来查找对应的所创建的对象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> 通过哈希表来储存数据,从哈希表查找数据的过程如下:

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

FastThreadLocal.png

需要注意的点:

上一篇 下一篇

猜你喜欢

热点阅读