ThreadLocal,神神秘秘的一个东西,长久以来似乎都觉得“这玩意好屌!竟然能这么轻松地解决线程间资源冲突问题!”。然而分析下它的源码就会发现,这东西只是唬人的,原理其实就是“在各线程的堆空间里维护各线程自己的资源”,更通俗的说法就是“废话!你让每个线程在自己线程里面用自己的局部变量,发生冲突才怪!”。所以说啊这个东西就是个纸老虎,下面从头分析。
分析前感谢这篇博客:http://www.cnblogs.com/dolphin0520/p/3920407.html,没有这篇我真的弄不懂,这篇观点正确,诲人不倦,一定是位技术深厚的前辈!
好,闲言少叙,讲正题。
先是一个使用ThreadLocal的例子:
private static final ThreadLocal<Integer> CONTEXT = new ThreadLocal<>();
public void setVal(int i) {
CONTEXT.set(i);
}
public int getVal(){
Integer val = CONTEXT.get();
return val == null ? 0 : val;
}
这是一个没什么用的例子,只为说明问题。setVal方法可以往现在这个线程的“线程本地空间”(先不用管这个名词啥意思,就当是线程间互不相关的各自的一块空间)里存进去一个Integer或者更新已有Integer的值;getVal方法是获取这个值。threadlocal的作用就在于可以让每个线程各有各的值,互不影响,线程安全!
那么从源码角度分析threadlocal是怎么做到的。
首先是看一下ThreadLocal.set(T value)这个方法:
/**
* 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);
}
来一行行看ThreadLocal在这里干了什么,先是获取当前线程,然后调用了getMap,传入当前线程作为参数,获得到了一个类型为ThreadLocalMap的对象。好,现在解决两个问题(注意接下来提到的类名):
1、ThreadLocalMap是啥?
跳过去看一下发现,ThreadLocalMap是ThreadLocal的一个内部类,ThreadLocalMap内部有一段这样的代码:
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
看到这里可能很惊奇,“纳尼?键的类型是ThreadLocal?”,暂且搁置这个疑问,Entry的值是Object,嗯,看来每个线程里的“数据副本”就是存在这。
2、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;
}
传进去的是当前线程,返回来的是当前线程对象里的一个成员变量!而且类型是ThreadLocalMap!“纳尼?!Thread类里有这个成员变量?”没错,Thread类里持有一个ThreadLocalMap对象!不信跳过去看,Thread类里有这句话:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
这是Thread类的一个成员变量。返回去的就是这个,每个Thread都自己有一个的,存在于该Thread栈空间的,和该Thread中声明的局部变量没什么区别的(从存储的角度上讲),一个Entry为<ThreadLocal, Object>的ThreadLocalMap。
回头看之前的ThreadLocal.set(T value),再贴一遍代码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
获取到了当前线程的ThreadLocalMap之后,我们的ThreadLocal判断了一下map是否为空,不空就存入或替换为(this, value);空就执行一个初始化的操作。set或者初始化的细节本文不赘述了。
值得注意的是,之前我们很疑惑的“键为什么是ThreadLocal”这个谜题解开了,这里不是就传入了this这个ThreadLocal嘛,这是因为一个Thread可能对应不止一个ThreadLocal,想要知道具体是Thread对应的哪个ThreadLocal,就要在Thread中维护一个ThreadLocalMap,以ThreadLocal为键,就可以找到Thread在某个ThreadLocal里对应的本地数据(本地数据指的就是Entry值的那个Object,例子里的Integer,实际上以thread内部变量的形式存在于thread对象中),这就是“Thread里为啥有个ThreadLocalMap?ThreadLocalMap为啥是个Map?键的类型为啥是ThreadLocal?”这三个问题的答案!
上面一段话比较拗口,本人水平所限,只能说成这样了,真正理解上面的话也就理解了ThreadLocal。这时候你一定会拍着大腿说,我靠!这是个什么玩意儿?原来这么简单啊!
没错,ThreadLocal的根本原理在于把数据存在了线程的各自的ThreadLocalMap中,也就是存在了线程的一个成员变量里,线程自己的内部变量当然跟别的线程互不影响,当然解决了这个问题。也就是说上面的例子里,你完全可以自己给你的线程类里加一个Integer型的成员,再写个get、set方法,就能达到完全相同的效果,这不就是所谓的“给每个线程一份数据副本”吗?只不过JAVA为你提供了一个名为ThreadLocal的API让你可以方便的处理这件事,比如你需要在方法间跳来跳去的时候,或者数据类型没有Integter这么简单的话。ThreadLocal不过是个方便你管理线程里数据的一个JDK提供的API而已,没什么神奇的。
回头看ThreadLocal这个名字,觉得像冷笑话一样,“线程本地”,意思是说“线程自己拿自己的本地空间(线程里的局部变量)存数据”。