Yuyy
Yuyy
Java:ThreadLocal

ThreadLocal结构图

http://bed.yuyy.info/image-20201219152616692.png

  • 结构变化后带来的好处
    • 早期ThreadLocalMap里的Entry较多,每个线程都有一个,占用空间大
    • 线程销毁时,后期的ThreadLocalMap将会销毁,释放资源
  • 设置默认值,也就是当前线程和ThreadLocal对象还未往ThreadLocalMap里存数据时
        ThreadLocal<String> threadLocal2 = new ThreadLocal<String>(){
              @Override
              protected String initialValue() {
                  return "hello";
              }
          };
          System.out.println(threadLocal2.get());
    
    • 因重写了ThreadLocal里的方法,该对象实际上是生成的一个匿名类,并继承了ThreadLocal

    http://bed.yuyy.info/image-20201219165719614.png

  • 我们看看ThreadLocal如何存数据的

    1. 获取当前线程的ThreadLocalMap
      public void set(T value) {
           Thread t = Thread.currentThread();
           ThreadLocalMap map = getMap(t);
           if (map != null)
               map.set(this, value);
           else
               createMap(t, value);
       }
      
    2. ThreadLocalMap的set方法
      private void set(ThreadLocal<?> key, Object value) {
      
               // We don't use a fast path as with get() because it is at
               // least as common to use set() to create new entries as
               // it is to replace existing ones, in which case, a fast
               // path would fail more often than not.
      
               Entry[] tab = table;
               int len = tab.length;
               int i = key.threadLocalHashCode & (len-1);
      
               for (Entry e = tab[i];//遍历当前线程的ThreadLocalMap
                    e != null;
                    e = tab[i = nextIndex(i, len)]) {
                   ThreadLocal<?> k = e.get();
      
                   if (k == key) {//发现key,则覆盖value
                       e.value = value;
                       return;
                   }
      
                   if (k == null) {//发现某个key对应的ThreadLocal对象已被回收
                       replaceStaleEntry(key, value, i);//详见下方解析
                       return;
                   }
               }
              tab[i] = new Entry(key, value);//没有找到key,说明是第一次存这个key
               int sz = ++size;
               if (!cleanSomeSlots(i, sz) && sz >= threshold)
                   rehash();
      
    3. new Entry
      static class Entry extends WeakReference<ThreadLocal<?>> {
               /** The value associated with this ThreadLocal. */
               Object value;
      
               Entry(ThreadLocal<?> k, Object v) {
                   super(k);//调用WeakReference的构造方法,使ThreadLocalMap里的key与ThreadLocal对象保持弱引用的关系,即不影响该对象的gc
                   value = v;
               }
           }
      
  • 上面的代码中,如果发现ThreadLocalMap里有key为null,会执行replaceStaleEntry(key, value, i);该方法里调用了expungeStaleEntry(int staleSlot)
      private int expungeStaleEntry(int staleSlot) {
          Entry[] tab = table;
          int len = tab.length;
          // expunge entry at staleSlot
          tab[staleSlot].value = null;//清理该key对应的value
          tab[staleSlot] = null;//清理该key
          size--;
    
          // Rehash until we encounter null
          Entry e;
          int i;
          for (i = nextIndex(staleSlot, len);//遍历ThreadLocalMap
               (e = tab[i]) != null;
               i = nextIndex(i, len)) {
              ThreadLocal<?> k = e.get();
              if (k == null) {//清理key为null的Entry
                  e.value = null;
                  tab[i] = null;
                  size--;
              } else {
                  int h = k.threadLocalHashCode & (len - 1);//重新计算hash
                  if (h != i) {//如果不是当前位置,将移动
                      tab[i] = null;//置空当前位置的Entry
    
                      // Unlike Knuth 6.4 Algorithm R, we must scan until
                      // null because multiple entries could have been stale.
                      while (tab[h] != null)//如果重新计算哈希得出的位置有值了,就往后找,直到找到个不为空的
                          h = nextIndex(h, len);
                      tab[h] = e;
                  }
              }
          }
          return i;
      }
    
  • 上诉方法是可以防止内存泄露,将为null的key对应的Entry清理。

  • ThreadLocalMap使用的是线性探查法,与此相关的算法有HashMap的拉链法

    http://bed.yuyy.info/20190306205818167.png

  • 回收过程

    • ThreadLocal引用销毁(这个词可能不专业)
    • ThreadLocal对象因没有强引用,只有个弱引用,gc时将回收。这就是为什么用弱引用,使用强引用时,会影响ThreadLocal对象的回收
    • 如果ThreadLocal对象被回收了,ThreadLocalMap里依旧存着该对象的虚引用为key的Entry(包括Value),这将会造成内存泄漏,如何避免呢?
  • 实际代码中,ThreadLocalMap会在set,get以及resize等方法中对stale slots做自动删除(set以及get不保证所有过期slots会在操作中会被删除,而resize则会删除threadLocalMap中所有的过期slots)。当然将threadLocal对象设置为null并不能完全避免内存泄露对象,最安全的办法仍然是调用ThreadLocal的remove方法,来彻底避免可能的内存泄露。

  • ThreadLocal对象可设置为静态变量,这样在任何地方都能方便的获取到该对象,执行remove。

发表评论

textsms
account_circle
email

Yuyy

Java:ThreadLocal
ThreadLocal结构图 结构变化后带来的好处 早期ThreadLocalMap里的Entry较多,每个线程都有一个,占用空间大 线程销毁时,后期的ThreadLocalMap将会销毁,释放资源 设置默认值,也…
扫描二维码继续阅读
2020-12-19
友情链接
标签
文章归档
近期文章