ThreadLocal

2018-05-01  本文已影响0人  海蟾子_null

ThreadLocal使用场景

ThreadLocal是绑定在Thread上的变量,生命周期与Thread相同。通常用于线程内数据共享。
例如线程内接口调用追踪,我们会在第一个调用处生成一个ID,在接下来的接口中,获取这个ID,打印出调用链,那么就可以用ThreadLocal很方便的传递这个调用链ID,而不影响其他线程的调用链ID的传递与生成。

源码解读

我们通常使用ThreadLocal的set,get和remove方法。
set处理代码片段:

    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
      //map不为空,则将当前变量与当前线程绑定
        if (map != null)
            map.set(this, value);
        else
        //为空,则创建map
            createMap(t, value);
    }

接下来看看createMap,源码如下

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
 ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
          //初始化一个entry数组
            table = new Entry[INITIAL_CAPACITY];
            //计算所在位置
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            //构造entry,并放入相应的位置
            table[i] = new Entry(firstKey, firstValue);
           //设置当前table元素个数
            size = 1;
            //设置容量调整阈值
            setThreshold(INITIAL_CAPACITY);
        }

若当前线程中有ThreadLocalMap,则将变量塞到map中去。源码如下:

 private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            //循环获取entry
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                //若变量已存在且没被gc,则更新值
                if (k == key) {
                    e.value = value;
                    return;
                }
                //找到位置了,删除无用的entry,将该entry放入相应的位置
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //变量不存在,则放入map
            tab[i] = new Entry(key, value);
             //table的元素个数+1
            int sz = ++size;
            //是否需要扩容
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

其中replaceStaleEntry方法如下,这是一坨复杂的逻辑,主要完成的就是清除过期的entry,然后将新的entry放入进来:

 private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;
            int slotToExpunge = staleSlot;
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

至此,set完成。
接下来看get源码:

    public T get() {
        Thread t = Thread.currentThread();
      //获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
      //Map 不为空,遍历map,找到相应的值,
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //Map为空,或者map里没有相应的值,则取初始值
        return setInitialValue();
    }

每个线程持有的ThreadLocalMap,实际上操作的都是Map的Entry。看下entry代码

  static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

从源码中可以看出,线程本地变量的隔离机制如下:
每个线程Thread都会有一个ThreadLocalMap的成员变量。Map的key值是定义的ThreadLocal实例。所有的线程里Map的ThreadLocal都指向同一个地址,只是每个线程的value值不一样。即同一个变量在每个线程里的值都不一样。到达了线程隔离的效果。

上一篇 下一篇

猜你喜欢

热点阅读