美文网首页
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