ThreadLocal浅析

来源:互联网 发布:win10优化 gui 编辑:程序博客网 时间:2024/04/29 13:14

1.目的

ThreadLocal目的是保存一些线程级别的全局变量,比如connection,或者事务上下文,避免这些值需要一直通过函数参数的方式一路传递。

2. 常见用法

public class Test2 {public static void main(String[] args) throws InterruptedException {testThreadLocal();}private static void testThreadLocal() {Util.setGlobalName("zili.dengzl");new Foo().printName();}}class Foo{public void printName(){System.out.println("globalName="+Util.getGlobalName());}}class Util {private static final ThreadLocal<String> globalName = new ThreadLocal<String>();public static String getGlobalName() {return globalName.get();}public static void setGlobalName(String name) {globalName.set(name);}}


3.实现分析

要实现上面这样的功能,最简单的想法是用一个Map<Thread,T>,如下:

class MockThreadLocal<T> {private Map<Thread, T> map = new HashMap<Thread, T>();public T get() {return (T) map.get(Thread.currentThread());}public void set(T value) {map.put(Thread.currentThread(), value);}}

这样也能实现ThreadLocal的效果,但是有一个问题,当对应的线程消失后,map中对应的线程值并不会被回收,从而造成内存泄露。

 

事实上ThreadLocal是这样做的:

    /**     * 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();        ThreadLocalMap map = getMap(t);        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null)                return (T)e.value;        }        return setInitialValue();    }


注意这里如果取到没有该线程对应的值,会调用setInitialValue();,最终调用initialValue()生成一个值,这也是我们很多场景下要override这个方法的原因;

 

下面看一下getMap(Thread t)方法:

    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }


在Thread类中:

    /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;

由此可见,所有的ThreadLocal的信息,最终是关联到Thread上的,线程消失后,对应的Thread对象也被回收,这时对应的ThreadLocal对象也会被回收。

这里为什么是一个ThreadLocalMap呢,因为一个线程可以有多个ThreadLocal变量,通过map.getEntry(this)取得对应的某个具体的变量。

        private Entry getEntry(ThreadLocal key) {            int i = key.threadLocalHashCode & (table.length - 1);            Entry e = table[i];            if (e != null && e.get() == key)                return e;            else                return getEntryAfterMiss(key, i, e);        }


最后要注意的一点是,ThreadLocalMap的Entry是一个weakReference:

       /**         * The entries in this hash map extend WeakReference, using         * its main ref field as the key (which is always a         * ThreadLocal object).  Note that null keys (i.e. entry.get()         * == null) mean that the key is no longer referenced, so the         * entry can be expunged from table.  Such entries are referred to         * as "stale entries" in the code that follows.         */        static class Entry extends WeakReference<ThreadLocal> {            /** The value associated with this ThreadLocal. */            Object value;            Entry(ThreadLocal k, Object v) {                super(k);                value = v;            }        }


这里主要因为ThreadLocalMap的key是ThreadLocal对象,如果某个ThreadLocal对象所有的强引用没有了,不能因为ThreadLocalMap的引用导致他不能被回收。

 

 

附:

这里补充一下weakReference的用法供参考(当强引用不存在时,下次垃圾回收会回收弱引用所引用的对象):

Object o = new Object();WeakReference<Object> ref = new WeakReference<Object>(o);System.out.println(ref.get());o=null;System.gc();System.out.println(ref.get());

结果输出: 

 java.lang.Object@de6ced null

 



 

 

原创粉丝点击