java基础-ThreadLocal

Posted holoyong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java基础-ThreadLocal相关的知识,希望对你有一定的参考价值。

来聊一下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进行显示释放。

 

以上是关于java基础-ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章

java基础-ThreadLocal

java多线程17:ThreadLocal源码剖析

Java多线程9:ThreadLocal源码剖析

源代码系列02——ThreadLocal源码分析(基础篇)

Java面试题必备知识之ThreadLocal

java 面试基础总结---多线程