ThreadLocal 实现

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

  1. ThreadLocal 的类结构
    ThreadLocal 有静态内部类 ThreadLocalMap, ThreadLocalMap 有静态内部类 Entry. Entry 是键值对, 存储. 就是说每个 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 {
    
        // 静态内部类 ThreadLocalMap 
        static class ThreadLocalMap {
            // ThreadLocal 静态内部类 Entry. 
            // Entry 的 key 是 ThreadLocal, value 是 ThreadLocal 中的值
            // 也因为 Entry 的 key 是 ThreadLocal, 所以其 value 只能有一个. 即 ThreadLocal 只能存储单个值
            static class Entry extends WeakReference> {
                /** 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 {
        // 实例化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 实现)