数据结构

https://www.cnblogs.com/wang-meng/p/12856648.html
ThreadLocal - 图1

  • 每个线程持有自己的ThreadLocalMap,是一个数组,数组的元素为Entity[]
  • Entity包含存入的弱引用的key(threadLocal对象)和value值
  • 获取元素时,使用key计算哈希槽位来定位到Entity的下标

    常用操作源码

    ```java /**
    • 设置当前线程的ThreadLocal变量的副本为指定的值 *
    • @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 map = getMap(t); if (map != null)
      1. map.set(this, value);
      else
      createMap(t, value);
      
      }

/**

  • 返回当前线程的ThreadLocal变量副本的值 *
  • @return the current thread’s value of this thread-local */ public T get() { Thread t = Thread.currentThread(); 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;
     }
    
    } return setInitialValue(); }
    <a name="Pj2Ix"></a>
    ### 添加元素的方式&hash冲突
    ```java
    private static int nextIndex(int i, int len) {
     return ((i + 1 < len) ? i + 1 : 0);
    }
    private static int prevIndex(int i, int len) {
     return ((i - 1 >= 0) ? i - 1 : len - 1);
    }
    private void set(ThreadLocal<?> key, Object value) {
     ThreadLocal.ThreadLocalMap.Entry[] tab = table;
     int len = tab.length;
     //计算索引,上面已经有说过。
     int i = key.threadLocalHashCode & (len-1);
     /**根据获取到的索引进行循环,如果当前索引上的table[i]不为空,在没有return的情况下,
     * 就使用nextIndex()获取下一个(上面提到到线性探测法)。*/
     for (ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null;
         e = tab[i = nextIndex(i, len)]) {
         ThreadLocal<?> k = e.get();
         //table[i]上key不为空,并且和当前key相同,更新value
         if (k == key) {
             e.value = value;
             return;
         }
         /**table[i]上的key为空,说明被回收了
          * 说明改table[i]可以重新使用,用新的key-value将其替换,并删除其他无效的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();//扩容
    }
    

    hash冲突

    通过开放寻址法,找到下一个可以使用的空位,则返回
    如果都找不到则扩容

扩容

  • 扩容前会对过期的key进行清理
  • 如果清理完Entity的size还是大于threshold阈值的3/4,则进行扩容,为原来大小的2倍

内存泄漏

弱引用内存泄露

image.png

  • key是弱引用,当ThreadLocal被回收时,key就为null
  • 但是线程对象Thread会存在于整个线程生命周期,所以value无法回收

为什么key是弱引用

  • ThreadLocalMap只能通过ThreadLocal间接调用方法,一旦ThreadLocal被回收,对应的Entity也就无法再获取
  • 所以为了在ThreadLocal所在对象被回收后自动回收,设计为弱引用
  • 等清理过期key时可以一同清除掉