美文网首页
ThreadLocal的正确认识

ThreadLocal的正确认识

作者: 雁阵惊寒_zhn | 来源:发表于2020-09-21 11:17 被阅读0次

    ThreadLocal是什么

    ThreadLocal能使线程中的某个值与保存值的对象关联起来,每个使用改变量的线程都存有一份独立的副本,因此返回的结果总是当前执行线程保存设置的最新值。

    上面是摘选自《Java并发编程实战》中文版中对ThreadLocal介绍,其中提到独立的副本,翻译为副本不是一个好的选择,会产生歧义。因为在中文里副本是与主本对应的,会给人一种错觉ThreadLocal存储的是对某一个变量深拷贝之后的变量。下面我们看看英文原版的介绍。

    Thread-Local provides get and set accessor methods that maintain a separate copy of the value for each thread that uses it, so a get returns the most recent value passed to set from the currently executing thread.

    在英文版中,用a separate copy of the value描述ThreadLocal的存储。这里的copy不应该简单的理解为拷贝复制,而应该理解为大量生产的样品中的一个。换言之,ThreadLocal保存的应该是防止全局共享的对象,大量产生后的一个。例如,数据库的连接Connection对象,由于JDBC的连接对象不是线程安全的,因此可以将JDBC的连接保存在ThreadLocal中,每个线程都拥有属于自己的连接。

    上面根据书中的叙述对ThreadLocal做了一个新的认识,但事实仍旧需要通过源代码进行验证,下面讲解ThreadLocal的源代码。

    ThreadLocal的set和get方法

    set方法

    public void set(T value) {
        //获得当前的线程
        Thread t = Thread.currentThread();
        //获取当前线程Thread对象的ThreadLocalMap结构对象
        ThreadLocalMap map = getMap(t);
        //如果map存在,设置值,key为当前的ThreadLocal对象,值为输入的value
        if (map != null)
            map.set(this, value);
        else//否则需要先创建map
            createMap(t, value);
    }
    

    get方法

    public T get() {
        //获得当前的线程
        Thread t = Thread.currentThread();
        //获取当前线程Thread对象的ThreadLocalMap结构对象
        ThreadLocalMap map = getMap(t);
        //map不为null时,当前的ThreadLocal对象为key查询到对应的Entry结构,返回value
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //否则,设置初始值null并且返回
        return setInitialValue();
    }
    

    上面两段代码是ThreadLocal的set和get方法的实现,注意到几点:

    1. 存储变量的数据结构ThreadLocalMap,是属于线程对象Thread自身的。
    2. ThreadLocalMap中的key是当前的ThreadLocal对象。

    上面两点简单说,就是ThreadLocal对象调用自己的方法,把自己作为key放入到当前调用自己的线程Thread对象的ThreadLocalMap结构中。

    1. ThreadLocalMap中把value和key封装为Entry结构进行存储

    第三点具体看下面Thread的ThreadLocalMap。

    Thread的ThreadLocalMap

    ThreadLocalMap的set方法

    把key-value封装为Entry结构存储到table中。通过计算ThreadLocal对象的哈希值找到对应的桶。

    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        //ThreadLocal计算哈希值找到对应的桶
        int i = key.threadLocalHashCode & (len-1);
        //遍历数组tab
        for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            //如果存在相同的key,替换value
            if (k == key) {
                e.value = value;
                return;
            }
            //如果查询到key是空的entry,将其清除并且放入新值
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        //否则,放入数组的最后的空位上
        tab[i] = new Entry(key, value);
        int sz = ++size;
        //如果需要,扩大数组大小
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }
    

    从上面的set方法可以看到key和value被封装为Entry结构存储,并且ThreadLocalMap底层是由Entry数组实现的。

    ThreadLocalMap中Entry的弱引用

    Entry结构是ThreadLocalMap的一个内部类,继承弱引用WeakReference实现。Entry的构造函数中key生成弱引用存储。
    弱引用指向的对象不会熬过第二次垃圾回收,这里使用弱引用是为了作为key的ThreadLocal对象被垃圾回收后,存储在ThreadLocalMap结构中的引用可以被自动回收掉,并且不可以再被使用了。这就是为什么ThreadLocalMap的set方法会查询到key为null的entry结构。只要作为key的ThreadLocal对象仍旧被强引用所指向,那么对象仍旧是可达的,不会被回收。

    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                  super(k);
                  value = v;
            }
        }
             .....省略余下代码......
    

    相关文章

      网友评论

          本文标题:ThreadLocal的正确认识

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