一、概述
ThreadLocal,即线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构。这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。可以通过set(T)方法来设置一个值,在当前线程下再通过get()方法获取到原先设置的值。
ThreadLocal实现的思路:Thread类中持有一个ThreadLocalMap的引用,用于存储每一个线程的变量副本,这个map在使用ThreadLocal变量时候被延迟创建和初始化,并在线程退出时候被释放。Map中元素的键为this所指向的ThreadLocal实例(并不是所以为的线程对象),而值对应需要使用的变量的副本。下面上源码
二、源码分析
1. 关于ThreadLocalMap属于Thread还是ThreadLocal之争
public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
以上可以看到Thread类中持有的ThreadLocalMap ();同时,注释中指出了这个变量由ThreadLocal类来维护,下面就是今天的主角ThreadLocal;所以,这个map是被线程所持有的,但是其初始化和维护都是在ThreadLocal中。
2. ThreadLocal中的四个方法
2.1 get方法 及其调用的方法
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();
}
/**
* 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;
}
/**
* 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;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
* @param map the map to store.
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
可以看到,threadLocals最初是从线程t获取。若尚未初始化,则调用 setInitialValue()
2.2 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);
}
这里就可以看到,map的key并非原以为的当前线程对象,而是this,这时候this的指向应该是当前的ThreadLocal对象
2.3 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);
}
无F可说
2.3 ThreadLocalMap–一个静态内部类
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
/**
* 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;
}
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
.
.
.
可以看到,ThreadLocalMap是ThreadLocal的一个静态内部类,其中又有一个继承了WeakReference的Entity,是一个定制化的hashMap.
三、show the code
public class LeThreadLocal extends Thread {
private static ThreadLocalValue threadLocalValue;
ThreadLocal<ThreadLocalValue> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
LeThreadLocal t = new LeThreadLocal();
t.start();
}
}
@Override
public void run() {
for (int j = 0; j < 5; j++) {
getThreadLocalValue().setIndex(getThreadLocalValue().getIndex() + 1);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
getThreadLocalValue().setS(Thread.currentThread().getName());
System.out.println(getThreadLocalValue());
}
}
public ThreadLocalValue getThreadLocalValue() {
if (threadLocal.get() == null) {
threadLocal.set(new ThreadLocalValue(null, 0));
getThreadLocalValue();
}
return threadLocal.get();
}
}
class ThreadLocalValue {
private String s;
private int index;
public String getS() {
return s;
}
public void setS(String s) {
this.s = s;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
public ThreadLocalValue(String s, int index) {
this.s = s;
this.index = index;
}
@Override
public String toString() {
return "ThreadLocalValue{" +
"s='" + s + '\'' +
", index=" + index +
'}';
}
}
测试输出
ThreadLocalValue{s='Thread-4', index=1}
ThreadLocalValue{s='Thread-0', index=1}
ThreadLocalValue{s='Thread-1', index=1}
ThreadLocalValue{s='Thread-2', index=1}
ThreadLocalValue{s='Thread-3', index=1}
ThreadLocalValue{s='Thread-4', index=2}
ThreadLocalValue{s='Thread-0', index=2}
ThreadLocalValue{s='Thread-3', index=2}
ThreadLocalValue{s='Thread-2', index=2}
ThreadLocalValue{s='Thread-1', index=2}
ThreadLocalValue{s='Thread-1', index=3}
ThreadLocalValue{s='Thread-4', index=3}
ThreadLocalValue{s='Thread-0', index=3}
ThreadLocalValue{s='Thread-3', index=3}
ThreadLocalValue{s='Thread-2', index=3}
ThreadLocalValue{s='Thread-1', index=4}
ThreadLocalValue{s='Thread-3', index=4}
ThreadLocalValue{s='Thread-4', index=4}
ThreadLocalValue{s='Thread-0', index=4}
ThreadLocalValue{s='Thread-2', index=4}
ThreadLocalValue{s='Thread-1', index=5}
ThreadLocalValue{s='Thread-0', index=5}
ThreadLocalValue{s='Thread-3', index=5}
ThreadLocalValue{s='Thread-4', index=5}
ThreadLocalValue{s='Thread-2', index=5}
分析: ThreadLocal变量的使用不同于普通变量,对于本例中的静态成员变量而言,应该在每次使用该变量的时候都首先在ThreadLocal中获取这个变量,而不是直接使用其引用进行操作。这也充分印证了,ThreadLocal为每个线程维护了一个副本,而不是多线程操作这个共享的变量。也就是说使用ThreadLocal类去维护一个变量,并不是为了让多线程共同去的效果叠加的操作这个变量,而是互不干涉的使用这个变量的副本,他们的效果不能叠加。
例子中的使用方式是,在可执行(Runnable)的类中维护一个ThreadLocal实例,并在初次使用需要隔离的变量时候,调用threadLocal.set(value)方法,并在后续操作和使用隔离变量value的时候都先在threadLocal中get,而不是直接操作变量的引用。
在Session维护的场景中也经常用到ThreadLocal,其使用方式是将比较宝贵的连接或者会话资源保存在threadLocal中,在这个线程的声明周期内都使用这个session;
易错点:
即便是ThreadLocal中变量,在使用时要先在threadLocal中get,而不是直接操作器引用,尤其是在类静态变量的使用上;要时刻想着去拿副本
在ThreadLocalMap中,key是当前threadLocal的实例,所以一个ThreadLocal只是维护了一个变量,如果有两个变量,那么需要用两个ThreadLocal的实例。
四、关于内存泄漏
(参考来源: http://blog.csdn.net/wudiyong22/article/details/52141608)
ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需要存储的Object。也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是图中的虚线,表示ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。
ThreadLocal为什么会内存泄漏
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
防止措施:
在每次使用完ThreadLocal,都调用它的remove()方法,清除数据