使用过的都知道ThreadLocal是一个线程的局部变量,JDK1.2开始就加入了此功能,确实为我们多线程编程带来方便。
当我们沉醉于欢喜之中,往往会带来一个致命的打击。这就是“戏“节。所以,接触任何事物的时候都必须知己知彼。
ThreadLocal一共有3个公共方法(构造方法除外):set,get,remove,也是我们最常用的方法,接下来一个个方法看看到底是怎么一回事。
set
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
从方法上面的官方描述可知道,set方法是设置当前线程的本地局部变量方法。那么当前线程是如何存储这个局部变量的呢?继续看getMap方法。
/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ 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;
线程类有一个ThreadLocalMap(ThreadLocal的一个静态内部类)属性,那就意味着每个线程实例都会有这个ThreadLocalMap变量,而这个ThreadLocalMap就是保存本地线程局部变量的一个容器,这个ThreadLocalMap是通过自己的一个内部类Entry数组保存变量的,如下所示:
/** * 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; } }
而这个Entry数组的下标i是通过ThreadLocal的hash等算法(具体算法参考代码)得出,也就是说,一个线程实例有一个ThreadLocalMap,这个ThreadLocalMap可以保存多个对象,而这个ThreadLocalMap所对应的key就是ThreadLocal对象本身的一个hash算法值。所以,定了多个不同的ThreadLocal静态对象,从这些ThreadLocal对象获取的得到的线程局部变量是根据自身变量的hash值作为key从本地线程的ThreadLocalMap获取出值。
/** * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */ private void set(ThreadLocal key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
get
从上面的set方法已经可以看出个大概,不过还是细节还是从代码捉起。
/** * 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(); }
首先也是通过本地线程获取ThreadLocalMap,再通过ThreadLocal作为key从这个ThreadLocalMap获取出内部类Entry,最后返回Entry的value,这个value就是当初set进去的对象。需要注意的是,如果获取的Entry为空,则直接返回空。
/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
/** * Returns the current thread's "initial value" for this * thread-local variable. This method will be invoked the first * time a thread accesses the variable with the {@link #get} * method, unless the thread previously invoked the {@link #set} * method, in which case the <tt>initialValue</tt> method will not * be invoked for the thread. Normally, this method is invoked at * most once per thread, but it may be invoked again in case of * subsequent invocations of {@link #remove} followed by {@link #get}. * * <p>This implementation simply returns <tt>null</tt>; if the * programmer desires thread-local variables to have an initial * value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be * subclassed, and this method overridden. Typically, an * anonymous inner class will be used. * * @return the initial value for this thread-local */ protected T initialValue() { return null; }
remove
/** * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * <tt>initialValue</tt> method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
源码可以看出同样是获取当前线程的ThreadLocalMap对象,在把ThreadLocal自己当key去移除Entry数组里面的值。
/** * Remove the entry for key. */ private void remove(ThreadLocal key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
总结
综上所述,ThreadLocal就是一个以自身对象为key把value(存储对象)存放到当前线程的ThreadLocalMap里面,而ThreadLocalMap就是一个数组结构的容器。但这里有三个地方需要特别注意的:
1、线程的堆栈大小是有上限的,具体看JVM的相关设置参数。如果对象过大或者ThreadLocal过多而导致当下线程的ThreadLocalMap所保存对象的总大小超过了线程堆栈大小,就会导致内存堆栈溢出。
2、本地线程的ThreadLocalMap里面的内部类Entry对象是一个弱引用对象,如上面截图所示。弱引用对象就意味着随时会被GC回收。可能导致返回对象为空。
3、在一些web应用项目上或者中间件服务器上会,会用到线程池,线程池就意味着线程的生命周期把握在线程池手上,一个线程可能会被使用N次再被销毁或者回收,那么就意味着线程实例里的ThreadLocalMap对象仍然存在,如果不对Map里面的Entry数组操作的话,里面的对象仍然不会改变,除非被GC回收。