ThreadLocal介绍
ThreadLocal支持泛型,ThreadLocal<T>,T代表的是线程本地变量,在多线程并发中,每一个线程都保存一份属于自己的T对象。任何线程对T的操作仅仅是本地操作,不会相互影响。
参考Android-Looper源码中定义的ThreadLocal<Looper>。
public final class Looper {
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
...
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
...
}
在Looper类中,定义了一个静态ThreadLocal<Looper>对象,可以利用set和get方法设置和获取Looper对象。我们都知道,Android消息机制是基于Looper的一个无限循环方法loop(),主线程或其他线程在此处休眠等待消息。loop是一个静态方法,它在执行时一定会拿到当前线程的本地Looper对象进,并获取Looper的西消息队列,因此,每一个线程都有自己独立的Looper对象,ThreadLocal可以实现线程变量的本地化。
ThreadLocal如何实现本地变量
先分析一下ThreadLocal的set/get方法,看一下内部如何存储对象。
ThreadLocal#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);
}
某线程创建一个Looper对象,根据当前线程获取Thread内部ThreadLocal.ThreadLocalMap对象,ThreadLocalMap中有一个Entry数组,Entry是键值对key与value的封装,key就是ThreadLocal对象,value就是当前线程存储Looper对象。Entry数组负责存储线程本地变量。
每一个线程Thread对象的ThreadLocalMap不同,ThreadLocal对象的作用就是提供一个key索引,索引对应的value就是其泛型指定的对象类型,如果有多个本地变量,那么再定义一些ThreadLocal<T>对象即可。
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
Entry继承WeakReference,Entry对key键ThreadLocal对象的引用是弱引用。当遇到一个变量是线程本地变量时,创建一个ThreadLocal对象,Entry数组的key值弱引用reference指向ThreadLocal对象。
ThreadLocal#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方法查找本地变量时,从Thread对象中找到ThreadLocalMap,从Map的Entry数组中根据ThreadLocal查找对应的Entry,每一种本地变量存储一个Entry。最后读取Entry的value。线程本地变量存储结构图如下。
利用ThreadLocal实现线程本地变量存储结构.jpg
总结
ThreadLocal实现线程本地变量的本质是将线程本地对象存储在当前Thread的ThreadLocalMap内部Entry数组,以键值对的形式进行存储,而键值对中的key正是ThreadLocal对象,对其引用是弱引用,value代表某一变量的对象。因此,ThreadLocal仅仅是作为一个 key让线程来获取value,存储位置在当前Thread的Map中。
Looper内部ThreadLocal对象是静态的,因此,所有Looper对象公用一份ThreadLocal,每一个线程保存的key是相同的对象,而创建Looper对象value是不同的,对每个线程来说,value是隔离的就可以,并且非静态的还会多创造很多对象,增加内存消耗。
有一个问题,为什么Map对ThreadLocal的引用是弱引用呢?个人理解,一般情况下,如果没有其他强引用,弱引用的对象会被垃圾回收,因此,若ThreadLocal对象没有了强引用,就很容易被回收掉,这时,Entry的key就是空值,从Thread到Map再到Entry的value有一条强引用链,导致value内存泄露,这种情况下只要线程对象一直存在,没有key的value就会一直存在,这条链已经无用,无法定位到value,导致内存泄露。
如果Map对ThreadLocal使用的强引用,若ThreadLocal对象没有了其他强引用,那么,这条从Thread到Entry的强引用链会伴随Thread对象一直存在,因为已经无法从其他途径找到ThreadLocal对象啦,谈何获取Entry的key和value呢?因此,强引用的后果是整个Entry对象导致内存泄露。所以还是用弱引用比较稳妥,即使只有key这一条链访问到它(ThreadLocal),因为是弱引用链,至少还能保证被回收,我们只需要手动清除value即可。
上面都提到,前提ThreadLocal对象已经没有了除了key值以外的强引用,在Looper中,ThreadLocal对象是作为静态对象存储的,每一个线程产生的Looper对象公有ThreadLocal对象,总会有Looper对象链路到ThreadLocal,这应该就是讲ThreadLocal设置static的另一个原因吧。其他的场景后续再分析。
任重而道远
网友评论