来聊一下ThreadLocal的实现原理和它的内存泄漏问题
首先来看一个官方示例,这里构造了一个ThreadId类,其作用是在每个线程中保存各自的id,此id全局唯一,通过get可以获取id。
1 private static class ThreadId { 2 // Atomic integer containing the next thread ID to be assigned 3 private static final AtomicInteger nextId = new AtomicInteger(1); 4 // Thread local variable containing each thread‘s ID 5 private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() { 6 @Override 7 protected Integer initialValue() { 8 return nextId.getAndIncrement(); 9 } 10 }; 11 // Returns the current thread‘s unique ID, assigning it if necessary 12 public static int get() { 13 return threadId.get(); 14 } 15 }
ThreadLocal的构造器是一个空函数,new一个ThreadLocal实例时,唯一的操作就是对threadLocalHashCode的初始化,很明显这是一个hash值,猜测后续会用到map了。
1 private final int threadLocalHashCode = nextHashCode();
再来看调用get时,发生了什么
/** * Returns the value in the current thread‘s copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread‘s value of this thread-local */ public T get() { Thread t = Thread.currentThread(); //Thread中有一个ThreadLocalMap类型的实例字段,获得当前线程的threadLocalMap,其实就是返回t.threadLocals ThreadLocalMap map = getMap(t); if (map != null) { //以当前ThreadLocal实例为key,获取entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") //得到entry中的value T result = (T)e.value; return result; } } //如果当前线程的threadLocalMap为null,或者当前threadLocal还未插入threadLocalMap,则进行相应初始化工作,无非就是初始化map、调用自定义的initialValue方法、将initialValue返回值包装成entry插入map return setInitialValue(); }
看一下map的构造
1 static class ThreadLocalMap { 2 ...... 3 static class Entry extends WeakReference<ThreadLocal<?>> { 4 /** The value associated with this ThreadLocal. */ 5 Object value; 6 7 Entry(ThreadLocal<?> k, Object v) { 8 //注意,这里key被包装成了一个WeakReference 9 super(k); 10 value = v; 11 } 12 } 13 ...... 14 private Entry[] table; 15 ...... 16 private Entry getEntry(ThreadLocal<?> key) { 17 //threadLocalHashCode在ThreadLocal初始化时就已生成,全局唯一。这里 18 int i = key.threadLocalHashCode & (table.length - 1); 19 Entry e = table[i]; 20 if (e != null && e.get() == key)//e.get()即从WeakReference中取得threadLocal 21 return e; 22 else 23 //有意思,一般的hash表都是使用一个链式结构来解决hash冲突,而这里当hash冲突时进行线性探测 24 return getEntryAfterMiss(key, i, e); 25 } 26 ...... 27 }
问题来了,为什么entry中的key要包装成WeakReference呢?
设想,当我们不再需要threadLocal了,以前例来说就是置ThreadId中类变量threadId为null(假设threadId不是final,也没有被其他引用),而Thread类中的threadLocalMap中仍然持有threadId的引用,这就会产生内存泄漏。将threadLocal包装成WeakReference作为key存储,当threadId为null时,该threadLocal在gc时就会被回收,但此时value还在,ThreadLocal会在进行其他操作时删除key为null的value。这确实存在一种内存泄漏隐患,如果之后不在进行ThreadLocal操作,就真释放不掉value了。通常我们需要被声明为ThreadLocal的变量,在运行期间都是不期望它被回收的,所以我们通常会将其声明为static final,如果有回收的需求,也请使用ThreadLocal的remove进行显示释放。
时间: 2024-10-26 17:36:36