美文网首页
ThreadLocal

ThreadLocal

作者: 海蟾子_null | 来源:发表于2018-05-01 20:32 被阅读0次

    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值不一样。即同一个变量在每个线程里的值都不一样。到达了线程隔离的效果。

    相关文章

      网友评论

          本文标题:ThreadLocal

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