美文网首页
ThreadLocal 实现

ThreadLocal 实现

作者: lj72808up | 来源:发表于2021-05-15 01:13 被阅读0次

    一. ThreadLocal 如何实现和每个 Thread 绑定, 从而避免线程安全问题

    1. ThreadLocal 的类结构
      ThreadLocal 有静态内部类 ThreadLocalMap, ThreadLocalMap 有静态内部类 Entry. Entry 是键值对, 存储<ThreadLocal, Object>. 就是说每个 ThreadLocal 对象对应一个 object 的value.

    2. 每个 thread 对象都有唯一的 threadLocalMap 属性, 而 threadLocalMap 对象呗限制在一个线程中. 而 threadLocalMap 内部的Entry 又能存储多个 threadLocal 对象, 从而让 threadLocal 对象是线程安全的

      /* ThreadLocal values pertaining to this thread. This map is maintained
       * by the ThreadLocal class. */
      ThreadLocal.ThreadLocalMap threadLocals = null;
      
    3. 为什么说 ThreadLocal 只能存储一个值? (Entry的关系)

      // 泛型
      public class ThreadLocal<T> {
      
          // 静态内部类 ThreadLocalMap 
          static class ThreadLocalMap {
              // ThreadLocal 静态内部类 Entry. 
              // Entry 的 key 是 ThreadLocal, value 是 ThreadLocal 中的值
              // 也因为 Entry 的 key 是 ThreadLocal, 所以其 value 只能有一个. 即 ThreadLocal 只能存储单个值
              static class Entry extends WeakReference<ThreadLocal<?>> {
                  /** The value associated with this ThreadLocal. */
                  Object value;
      
                  Entry(ThreadLocal<?> k, Object v) {
                      super(k);
                      value = v;
                  }
              }
      
              // ThreadLocalMap 构造函数. 就是一个哈希表
              ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
                  table = new Entry[INITIAL_CAPACITY];
                  int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
                  table[i] = new Entry(firstKey, firstValue);
                  size = 1;
                  setThreshold(INITIAL_CAPACITY);
              }
          }
      }
      
    4. ThreadLocalMap 底层实际是一个 hash 表, 用 ThreadLocal 的 hashcode 值决定在 hash 表中的位置. 那如何让线程内, 每次 new ThreadLocal() 获得的对象其 hashCode 不一样呢?
      因为有一个类的全局静态属性 AtomicInteger 的 nextHashCode ,每次实例化对象时修改该值

      public class ThreadLocal<T> {
          // 实例化ThreadLocal 对象时, 使用 threadLocalHashCode 唯一标识一个 ThreadLocal 对象
          private final int threadLocalHashCode = nextHashCode();
          // 类级别属性, 每个 ThreadLocal 对象共享 nextHashCode 属性
          private static AtomicInteger nextHashCode = new AtomicInteger();
          // 常亮
          private static final int HASH_INCREMENT = 0x61c88647;
          private static int nextHashCode() {
                return nextHashCode.getAndAdd(HASH_INCREMENT);
          }
      }
      
    5. 为什么 ThreadLocal 有内存泄漏问题
      因为每个 thread 对应唯一的 threadLocalMap 对象, 但 ThreadLocalMap.Entry 键值对内部可以存储多个 ThreadLocal key, 且这个 key 是 WeakReference. 如果代码中没有对 ThreadLocal 的强引用, 一旦 gc, 该 ThreadLocal 就会被置空, ThreadLocalMap.Entry 原来存储该 ThreadLocal 的 key 就会被置为 null, 从而客户端再也无法访问 key=null 的键值对对应的 value, 导致 value 泄露, 再也无法 gc.
      其实 jdk 内部在 threadLocal 对象的 get() 或 set(). remove() 方法内有对 ThreadLocalMap.Entry 中 k=null 键值对的清除操作. 只是该操作必须让客户端触发 get() 或 set(). remove() 方法. 所以 jdk 鼓励在 threadLocal 对象不再使用时, 用 threadLocal.remove() 方法清除. (不能直接置为 null , 这和软引用 gc 导致内存泄露的做法一样).

      static class ThreadLocalMap {
          // ThreadLocalMap 构造函数
          ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
              table = new Entry[INITIAL_CAPACITY];
              int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
              table[i] = new Entry(firstKey, firstValue);
              size = 1;
              setThreshold(INITIAL_CAPACITY);
          }
      
      
          // 获取键值对
          private Entry getEntry(ThreadLocal<?> key) {
              // 根据 ThreadLocal 对象的 hashcode 计算出当时 set 的数组位置
              int i = key.threadLocalHashCode & (table.length - 1);
              Entry e = table[i];
              if (e != null && e.get() == key)
                  return e;
              else
                  // 当 direct hash slot 中没有 ThreadLocal 对象时, 向下一个 slot 查找
                  return getEntryAfterMiss(key, i, e);
          }
      
      
          // 向下一个 slot 查找 ThradLocal 对象
          private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
                  Entry[] tab = table;
                  int len = tab.length;
      
                  while (e != null) {
                      ThreadLocal<?> k = e.get();
                      if (k == key)
                          return e;
                      // 遇到 key 为 null 的, 就是已经被 jvm 清除的 ThreadLocal 弱引用 
                      // (只要没有强引用,一旦 gc , hreadLocal 对象就会被置空)
                      if (k == null)
                          // 该方法清除 key 为 null 的slot, 会缩减内部 hash 表的长度. 具体算法略
                          expungeStaleEntry(i);
                      else
                          i = nextIndex(i, len);
                      e = tab[i];
                  }
                  return null;
              }
      }
      

    相关文章

      网友评论

          本文标题:ThreadLocal 实现

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