一、使用场景
ThreadLocal用于不同线程获取各自数据,同一个线程也可根据不同的threadlocal对象获取到各自的数据。
二、源码解析
ThreadLocal如何实现不同线程获取各自数据的呢?其实源码中使用到ThreadLocal的场景有很多,下面通过Looper来分析ThreadLocal的实现原理。
Android基于消息机制运行,每个线程都有自己的消息队列,通过Looper不断循环获取消息,进行操作。我们在不同线程中调用Looper.myLooper()可以获取到各自线程的Looper对象,我们没有传入线程的相关数据,那么是如何实现的呢?
static final ThreadLocal sThreadLocal = new ThreadLocal();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
很显然,通过ThreadLocal的get方法实现,我们继续看ThreadLocal
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
在ThreadLocal的get方法中首先获取了当前线程,接着获取到此线程的ThreadLocalMap对象,通过源码可以得知,它是数据存储类。好了,原来获取到的就是每个线程的数据存储类,当然可以实现不同线程获取各自的数据了。当我们获取到ThreadLocalMap之后,接下来就要取出其中的value,也就是Looper,这里有两个分支。
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
当map不为null时,当前的ThreadLocal作为key,获取value,所以(划重点)ThreadLocal不只可以实现存储不同线程的数据,也可以实现存储同一线程中不同ThreadLocal对象的数据。
当map为null时,首先获取初识value,默认是null,可以自己重写initialValue赋值,接着调用creatMap
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存储对象,并将当前ThreadLocal对象作为key,赋值初始的value。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
调用createMap只有两个地方,set和setInitialValue,下面我们来看set
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,若map不为null,则将当前ThreadLocal对象作为key,添加value,若为null,则创建ThreadLocalMap。
我们回到Looper,调用set的只有一个地方,就是prepare,主线程即UI线程会自动调用prepare,将Looper对象作为value放置到主线程的ThreadLocalMap中;所以这就是为什么子线程使用Looper时候,先手动调用prepare的原因了。
三、总结
1、在各自线程中创建其数据存储类ThreadLocalMap,并赋值给线程中的变量threadLocals;获取数据时,直接取出当前线程的threadLocals,即获得此线程的数据存储类。所以不同线程可获取到各自的数据。
2、由于ThreadLocalMap以当前ThreadLocal作为key,所以也可实现同一线程的不同ThreadLocal对象获取到各自的数据。