一、Handler机制与ThreadLocal
在Handler机制的时候,我们会接触到Looper中的一个很重要的类:ThreadLocal,ThreadLocal并不是线程,它的作用是可以在每个线程中存储数据。
Handler在创建的时候就会获取当前线程的Looper来构造消息循环系统,获取的方式就是通过ThreadLocal。ThreadLocal可以在不同的线程中互补干扰的存储并提供数据,Handler就是通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是,线程是默认没有Looper的,如果需要使用Handler就必须为线程创建Looper。
大家经常提到的主线程,也叫UI线程,它就是ActivityThread,ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。
二、ThreadLocal 基本使用
ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有指定线程中可以获取到存储的数据,对于其它线程来说无法获取到数据。一般开发情景中我们用不到ThreadLocal,但是在一些非常复杂或者特殊的情景中, 通过ThreadLocal可以轻松地实现一些看起来很复杂的功能。
在Android的系统机制中,以下源码中使用到了ThreadLocal:
- Looper
- ActivityThread
- ActivityManagerService
具体到ThreadLocal的使用场景,一般可以概况为,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。
下面是使用ThreadLocal的最简单的例子:
public class ThreadLocalTest {
static ThreadLocal threadLocal = new ThreadLocal();
public static void main(String[] args) {
threadLocal.set(true);
System.out.println("Main ThreadLocal Value = " + threadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set(false);
System.out.println("ThreadLocal_1 Value = " + threadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("ThreadLocal_2 Value = " + threadLocal.get());
}
}).start();
}
}
输出的结果如下:
Main ThreadLocal Value = true
ThreadLocal_1 Value = false
ThreadLocal_2 Value = null
可以看出,虽然在不同线程中访问的是同一个ThreadLocal对象,但是它们通过ThreadLocal来获取到的值却是不一样的,这就是ThreadLocal的奇妙之处。
三、ThreadLocal源码
View Code
ThreadLocal 暴露了5个基本的操作和构造方法,主要的功能有:构造方法、设值方法、取值方法和资源回收;
1. 构造方法
ThreadLocal 是一个泛型类,只提供了一个构造方法,通过泛型可以指定要存储的值的类型;这个构造方法通常可以单独使用,也可以配合initialValue方法在实例化对象时提供一个初始值。
2.设值方法set
保存数据可以使用set方法,多次调用set方法不会保存多个数据,而是会覆盖掉。一个ThreadLocal只能保存一个数据:
public void set(T value) {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 拿到当前线程中的ThreadLocalMap
if (map != null)
map.set(this, value); // 线程中存在ThreadLocalMap,设值
else
createMap(t, value); // 线程中不存在ThreadLocalMap,创建后再设值
}
3.取值方法get
在没有使用set方法设值之前,调用get方法获取到的值将是initialValue方法设置的值(如果此方法未被覆盖则返回null)。调用set方法设值之后,返回的就是设置的值。
public T get() {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 拿到当前线程保存的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 这里传入的this就是当前的ThreadLocal对象,拿到ThreadLocal对应的值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value; // 拿到ThreadLocal的值
return result;
}
}
return setInitialValue(); // 调用setInitialValue方法返回初始值
}
4.资源回收remove
当我们不再需要保存的数据时,应该通过remove方法将当前线程中保存的值移除掉使对象得到GC(调用remove方法将把ThreadLocal对象从当前线程的ThreadLocalMap移除):
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread()); // 拿到当前线程中的ThreadLocalMap
if (m != null) {
m.remove(this); // 从ThreadLocalMap移除key为当前ThreadLocal对象的记录
}
}
调用remove方法会清空使用set方法设置的值,此时如果再次调用了get方法,由于ThreadLocal对应的记录已经不存在,所以将会执行return setInitialValue();这段代码,这里将会调用initialValue方法从而返回初始值。
四、ThreadLocal 内存泄漏问题
ThreadLocalMap中的Entry中,ThreadLocal作为key,是作为弱引用进行存储的。当ThreadLocal不再被作为强引用持有时,会被GC回收,这时ThreadLocalMap对应的ThreadLocal就变成了null。而根据文档所叙述的,当key == null时,这时就可以默认该键不再被引用,该Entry就可以被直接清除,该清除行为会在Entry本身的set()/get()/remove()中被调用。
一般情况下,线程在执行结束时,自然也会消除其对value的引用,使得Value能够被GC回收。但是在使用线程池或者其他特殊情况下的时候,会存在线程复用的情况,这时候Value的值依然能获取到,可能就存在内存泄漏的隐患了。所以我们推荐通过手动将value的值设置为null(即调用ThreadLocal.remove()方法)以规避内存泄漏的风险。
五、推荐资料
-
《Java并发编程的艺术》