ThreadLocal即线程局部变量的意思!所以什么是线程局部变量?这玩意有什么鸟用?是不是面试被问到了说不出个一二三?今天就来扒一扒这货的源码,从根本上了解这货是干啥的。
其实研究下来他的源码实现,其实也没想象的那么复杂,其最主要有以下几点:
1、Java可以通过Thread.currentThread()
来获得当前的Thread的实例对象。既然能拿到这Thread对象实例,那么我们就可以操作该实例(的属性),比如为该Thread对象设置一个值什么。
2、每一个Thread对象都有一个ThradLocalMap实例,该实例有一个一个Entry组成的数组,Entry对象有两个主要属性:value和ThreadLocal的弱引用,其中value这个属性就是值设置给当前线程所持有,也是ThreadLocal的核心属性:
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
注意Entry继承自WeakReference,其key就是ThreadLocal对象!(图1)
结合1和2两个知识点,我们就可以知道我们拿到Thread对象之后,就可以操控当前线程对象的ThreadLocalMap对象,然后把想要保存的value交给ThreadLocalMap的Entry的value属性,Thread,ThreadLocalMap,value三者之间的关系可以用下图表示(图2):
通过上图我们可以得出这么一个结论:一个Thread对象持有一个ThreadLocalMap对象,然后呢,一个ThreadLoalMap对象又包含了多个ThreadLlocal对象及ThreadLocal对象所在线程的value!!!一言以蔽之: 一个Thread对象可以持有多个ThreadLocal对象的变量值value
那么ThreadLocal和Thread又有啥关系呢?二者是怎能对value进行读取的呢?下面就根据源码来简单的分析下。
先看看ThreadLocal的set方法:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程持有的ThreadLocalMap
ThreadLocal.ThreadLocalMap map = getMap(t);
//将value设置给threadlocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法很逻辑很简单(j结合上图2看更好理解):
1、通过currentThread方法拿到当前Thread对象
2、获取当前Thread对象的ThreadLoalMap对象
3、将value连同ThreadLocal对象自己组成一个Entry对象保存在
ThreadLoalMap的Entry类型的数组中。
在来看看ThreadLocal的get方法:
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
//获取与ThreadLocal对象想关联的value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//获取值
T result = (T)e.value;
return result;
}
}
//为空返回初始化值
return setInitialValue();
}
可以发展get的整体逻辑也很简单:
1、获取当前Thread对象
2、获取当前Thread对象的ThreadLocalMap对象
3、从ThreadLocalMap中获取与ThreadLocal相关联的Entry对象,具体的就是以ThreadLocal为key获取。
4、获取步骤3的Entry的value属性,并返回之。
通过整体观察get和set方法可以得出如下结论:ThreadLocal对象调用set方法就是往Thread对象的ThreadLocalMap里面添加值;ThreadLocal对象调用get方法就是从Thread对象的ThreadLocalMap里面获取值。核心就是操纵Thread对象的ThreadLocalMap对象进行value的读和写。原理就这么简单。
那么位于不同线程的不同ThreadLocal对象,在其他线程里保存值是一个什么样的关系呢?可以通过下图来清晰的描述出来:
通过上图可以知道我们在线程X里面定义了一个threadLocalX的变量,然后线程A调用threadLocalX的set方法将x1这个值设置给了线程A,线程B同样调用threadLocalX的set方法将x2这个值这是给了线程B.当我们操纵threadLocalX的get方法来取值的时候,此时get方法运行在哪个线程,取得就是哪个线程保存的值,比如如果此时A线程在执行,那么拿到的值就是x1.
通过博主的Android的消息机制详解这篇博文中,我们在知道在Android中一个线程只有一个Looper对象,那么是怎么做到的呢?就是ThreadLocal发挥了作用,看看Looper的prepare方法:
//定义一个静态的ThreadLocal变量
static final ThreadLocal sThreadLocal = new ThreadLocal();
private static void prepare(boolean quitAllowed) {
//一个Thread只能关联一个Looper对象
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
观察prepare方法可以知道,先通过sThreadLocal的get方法判断当前线程是否已经拥有了一个Looper对象,如果有就抛出一个异常;如果当前线程还没有设置Looper对象,则调用ThreadLocal的set方法,初始化一个Looper对象交给当前线程:sThreadLocal.set(new Looper(quitAllowed));
这样就确保了一个线程只有一个Looper对象。
线程局部变量到此也就好理解了,其实就是局部于线程对象的变量。在Java中Thread也是一个对象,平时开发中我们自定义一个对象也会持有一个List或者Map集合来持有一些数据,比如:
class MyObject{
List values;
//or
Map maps;
}
而Thread只不过是用了ThreadLocalMap而已:
class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null
}
到此为止,关于ThreadLocal的原理已经基本分析完毕,至于内部是怎么set和get的,敬请期待后续博文。
参考资料:
ThreadLocal源码分析