ThreadLocal这个名臣带有一定的迷惑性,千万不要认为ThreadLocal是线程的一种实现,网上很多人认为它应该叫ThreadLocalVariable更贴切,我对此也非常赞同。ThreadLocal存在的意义就是为了解决线程之间数据数据的冲突,ThreadLocal是线程的局部变量,它里面的数据只存活在线程的声明周期之中,而且必须是当前线程才能获取到对应的数据,其他的线程不能获取到当前线程的ThreadLocal中的数据。
在对ThreadLocal做了一个简单的介绍后,下面开始逐一分析。
1.ThreadLocal是什么?
ThreadLocal的本质是线程局部变量,可以理解为ThreadLocalVariable,它并非是线程的本地实现,不是一个Thread。
ThreadLocal的源码中类声明如下:
public class ThreadLocal<T> {...
2.ThreadLocal的作用?
ThreadLocal存在是为了解决什么问题的呢?在多线程并发的情况下,各个线程的在其上下文环境下保存一些变量副本,
可以独立的改变自己的副本,而不会和其他线程的副本冲突。
ThreadLocal的数据实际存储在ThreadLocalMap中,并且以当前的ThreadLocal对象作为key,当对ThreadLocal做get和set方法的时候,都是以this为key进行操作的,因此其的ThreadLocal对象获取当前的ThreadLocal对象的值。
set
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; }
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(); }
3.ThreadLocal中的变量存储在何处?
ThreadLocal中数据式存储在ThreadLocal.ThreadLocalMap中的。这个ThreadLocalMap的对象是被Thread对象的threadLocals的属性引用的。由此可见,ThreadLocal中变量存在Thread对象中,因此在线程的存活期间,如果没有刻意的去删除这些属性,实际上在线程声明周期中均可以对这些变量进行访问和操作。
ThreadLocalMap是作为Thread类的一个字段。
class Thread implements Runnable { /* Make sure registerNatives is the first thing <clinit> does. */ private static native void registerNatives(); static { registerNatives(); } private char name[]; private int priority; private Thread threadQ; private long eetop; /* Whether or not to single_step this thread. */ private boolean single_step; /* Whether or not the thread is a daemon thread. */ private boolean daemon = false; /* JVM state */ private boolean stillborn = false; /* What will be run. */ private Runnable target; /* The group of this thread */ private ThreadGroup group; /* The context ClassLoader for this thread */ private ClassLoader contextClassLoader; /* The inherited AccessControlContext of this thread */ private AccessControlContext inheritedAccessControlContext; /* For autonumbering anonymous threads. */ private static int threadInitNumber; private static synchronized int nextThreadNum() { return threadInitNumber++; } /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ //请看这里---- ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap何时创建,并将对象引用和Thread的threadLocals属性关联上呢?在ThreadLocal中有一个createMap的方法,在这个方法里会创建一个ThreadLocalMap。请看下面代码:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
此时还没有回答何时回创建ThreadLocalMap这个问题。实际上在前面的set方法中已经看到调用createMap的方法了,说明在第一设置数据的时候,如果使用当前ThreadLocal对象没有找到ThreadLocalMap对象的时候,那么将会去创建ThreadLocalMap对象,但是起初没有set就开始调用get方法呢?在get方法里面调用一个叫setInitialValue的方法。事实上setInitialValue和set方法非常相似,在setInitialValue中也调用createMap方法。请看下面代码:
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; }
至此,我可以知道了ThreadLocalMap是存储在Thread对象中,其次TheadLocal会在首次调用get或者set的时候创建ThreadLocalMap对象,并且将这个ThreadLocalMap的对象赋给当前线程的threadlocals属性。
4.ThreadLocal中数据存储详细分析。
(1)ThreadLocal中数据是存储在ThreadLocalMap的对象中。并且是ThreadLocalMap对象和Thread对象时一对一的关系。
(2)一个ThreadLocalMap对象对应的ThreadLocal对象是N个。
(3)ThreadLocalMap的Entry是一个WeakReference<ThreadLocal>子类,ThreadLocalMap中存储的时候是以ThreadLocal的对象作为key,当key释放了引用(key==null),ThreadLocalMap会立刻移除对应Entry。
我们再次看一下ThreadLocalMap.Entry的声明:
static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } }
5.ThreadLocal回收。参考资料来自ThreadLocal内存泄露
被废弃了的ThreadLocal所绑定对象的引用,会在以下4情况被清理。
如果此时外部没有绑定对象的引用,则该绑定对象就能被回收了:
1 Thread结束时。
2 当Thread的ThreadLocalMap的threshold超过最大值时。
3 向Thread的ThreadLocalMap中存放一个ThreadLocal,hash算法没有命中既有Entry,而需要新建一个Entry时。
4 手工通过ThreadLocal的remove()方法或set(null)。
因此如果我们粗暴的把ThreadLocal设置null,而不调用remove()方法或set(null),那么就可能造成ThreadLocal绑定的对象长期也能被回收,因而产出内存泄露。