一. ThreadLocal
如何实现和每个 Thread
绑定, 从而避免线程安全问题
-
ThreadLocal 的类结构
ThreadLocal 有静态内部类 ThreadLocalMap, ThreadLocalMap 有静态内部类 Entry. Entry 是键值对, 存储<ThreadLocal, Object>. 就是说每个 ThreadLocal 对象对应一个 object 的value. -
每个 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;
-
为什么说 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); } } }
-
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); } }
-
为什么 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; } }
网友评论