小林家的ThreadLocal

2020-07-24  本文已影响0人  我永远喜欢御坂美琴

一、什么是ThreadLocal

本文ThreadLocal基于jdk1.8

ThreadLocal提供了线程的“局部变量”。该变量为每个线程自身独有,是其他线程无法访问的。实现了线程数据之间的隔离。我们可以通过ThreadLocalgetset方法来存取所谓的线程局部变量。

二、ThreadLocal原理

变量的线程私有是通过ThreadLocalThread中维护的ThreadLocalMap共同实现的。

每一个线程之中维护了一个ThreadLocalMapThreadLocal将自身作为key,将值存入Map中。当线程需要取值时,再利用ThreadLocal对象在当前线程的ThreadLocalMap中将值取出来。

下面是get和set方法的源码

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //将默认值set进去并返回默认值
        return setInitialValue();
    }
    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        //获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            //新建map
            createMap(t, value);
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

三、ThreadLocalMap

ThreadLocalMap构造方法

    /**
     * Construct a new map initially containing (firstKey, firstValue).
     * ThreadLocalMaps are constructed lazily, so we only create
     * one when we have at least one entry to put in it.
     */
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        //新建Entry数组
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        //设置阈值
        setThreshold(INITIAL_CAPACITY);
    }

    /**
     * Construct a new map including all Inheritable ThreadLocals
     * from given parent map. Called only by createInheritedMap.
     *
     * @param parentMap the map associated with parent thread.
     */
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];

        for (int j = 0; j < len; j++) {
            Entry e = parentTable[j];
            if (e != null) {
                @SuppressWarnings("unchecked")
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                if (key != null) {
                    Object value = key.childValue(e.value);
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while (table[h] != null)
                        h = nextIndex(h, len);
                    table[h] = c;
                    size++;
                }
            }
        }
    }

总的来说还是比较简单的,如果你对HashMap较为了解的话。ThreadLocalMap的初始容量为16,负载因子为0.75,每次扩容数组长度变为原来的两倍。值得注意的是,当出现冲突时,ThreadLocalMap使用的是线性探测法

四、内存泄漏问题

ThreadLocalMap使用弱引用来避免ThreadLocal对象的内存泄漏问题。

图中虚线表示弱引用,实线表示强引用。

若ThreadLocal对象失去了引用,即无法再访问该对象时。若此时Entry的key为强引用即还存在强引用链,该ThreadLocal对象无法回收。现在为弱引用,则垃圾收集器将会对其进行回收。当ThreadLocal对象被回收时,Entry 的key被置为null。

多说几句:

弱引用软引用的区别在于:只具有弱引用的对象拥有更短暂生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定很快发现那些只具有弱引用的对象。

到这里,我们知道了使用弱引用解决ThreadLocal对象的内存泄漏问题。但是对于Entry对象,仍然存在强引用链,垃圾回收器仍无法对其进行垃圾回收。所以建议当变量使用完之后应该手动remove掉,避免内存泄漏。

但是,ThreadLocalMap对于该问题有一些补救措施。

  1. ThreadLocalMap由Thread维护,生命周期同Thread对象保持一致。(这准确来说并不算补救措施,应该是Thread的特性)
  2. ThreadLocalMap 的 set 方法中,通过 replaceStaleEntry 方法将所有键为 null 的 Entry 的值设置为 null,从而使得该值可被回收。另外,会在 rehash 方法中通过 expungeStaleEntry 方法将键和值为 null 的 Entry 设置为 null 从而使得该 Entry 可被回收。通过这种方式,ThreadLocal 可防止内存泄漏。
    private void set(ThreadLocal<?> key, Object value) {
      Entry[] tab = table;
      int len = tab.length;
      int i = key.threadLocalHashCode & (len-1);

      for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        if (k == key) {
          e.value = value;
          return;
        }
        if (k == null) {
          replaceStaleEntry(key, value, i);
          return;
        }
      }
      tab[i] = new Entry(key, value);
      int sz = ++size;
      if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
    }

但是!在线程池的使用场景中,若线程池的工作线程不被销毁则还是会出现内存泄漏问题,还是需要将使用完的ThreadLocal值进行remove。


参考文献:

Java进阶(七)正确理解Thread Local的原理与适用场景

ThreadLocal就是这么简单

理解Java的强引用、软引用、弱引用和虚引用


下面是私货时间:

上一篇下一篇

猜你喜欢

热点阅读