ThreadLocal 经常会在 Android 面试中碰到,回答关于 Android 消息机制的面试题时,随着面试官的深入就会有 ThreadLocal 的问题。本文将聊聊我对 ThreadLocal 的认识,希望能够帮助大家更好的理解 ThreadLocal。
ThreadLocal 是什么
ThreadLocal 的作用是提供线程内的局部变量,这种变量在多线程环境下访问时能够保证各个线程里变量的独立性。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。
ThreadLocal 如何使用
创建
ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
set 方法
mBooleanThreadLocal.set(true);
get 方法
mBooleanThreadLocal.get();
完整示例
private static ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
private static void testThreadLocal() {
mBooleanThreadLocal.set(true);
System.out.println("Thread#main mBooleanThreadLocal=" + mBooleanThreadLocal.get());
new Thread("Thread1") {
@Override
public void run() {
mBooleanThreadLocal.set(false);
System.out.println("Thread#1 mBooleanThreadLocal=" + mBooleanThreadLocal.get());
}
}.start();
new Thread("Thread2") {
@Override
public void run() {
System.out.println("Thread#2 mBooleanThreadLocal=" + mBooleanThreadLocal.get());
}
}.start();
}
日志如下
Thread#main mBooleanThreadLocal=true
Thread#1 mBooleanThreadLocal=false
Thread#2 mBooleanThreadLocal=null
从日志可以看出,虽然在不同线程中访问的是同一个 ThreadLocal 对象,但是它们通过 ThreadLocal 获取到的值是不一样的。
ThreadLocal 在 Android 中的运用
在 Android 中,Looper、ActivityThread 以及 AMS 中都有用到了 ThreadLocal。Looper类就是利用了ThreadLocal的特性,保证每个线程只存在一个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));
}
ThreadLocal 实现原理
ThreadLocal 内部变量定义
// HashCode
private final int threadLocalHashCode = nextHashCode();
// 下一个给出的 HashCode,从0开始
private static AtomicInteger nextHashCode = new AtomicInteger();
// HashCode 每次新增的值
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
这个类提供了线程局部变量。这个类能使线程中的某个值与保存值的对象关联起来,例如: “threadLocal.set(5)”,会将 “threadLocal” 和 “5” 作为键值对保存在该线程的 threadLocals 里。ThreadLocal 提供了 get 与 set 等访问接口或方法,这些方法为每个使用该变量的线程都存有一份独立的副本(即每个线程的 threadLocals 属性),因此 get 总是返回由当前执行线程在调用 set 时设置的最新值。
ThreadLocalMap 的定义
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap 是一个自定义哈希映射,仅用于维护线程本地变量值。ThreadLocalMap 是ThreadLocal 的内部类,主要有一个 Entry 数组,Entry 的 key 为 ThreadLocal,value 为ThreadLocal 对应的值。每个线程都有一个 ThreadLocalMap 类型的 threadLocals 变量。
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);
}
- 先拿到当前线程,再使用 getMap 方法拿到当前线程的 threadLocals 变量
- 如果 threadLocals 不为空,则将当前 ThreadLocal 作为 key,传入的值作为 value,调用 set 方法插入 threadLocals。
- 如果 threadLocals 为空则调用创建一个 ThreadLocalMap,并新建一个 Entry 放入该 ThreadLocalMap, 调用 set 方法的 ThreadLocal 和传入的 value 作为该 Entry 的 key 和 value
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 获取的实际上是Thread对象的threadLocals变量
}
而如果一开始设置,即ThreadLocalMap对象未创建,则新建ThreadLocalMap对象,并设置初始值。
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
get 方法
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();
}
- 跟 set 方法差不多,先拿到当前的线程,再使用 getMap 方法拿到当前线程的 threadLocals 变量
- 如果 threadLocals 不为空,则将调用 get 方法的 ThreadLocal 作为 key,调用 getEntry 方法找到对应的 Entry。
- 如果 threadLocals 为空或者找不到目标 Entry,则调用 setInitialValue 方法进行初始化。
从 ThreadLoacl 的 set 和 get 方法可以看出,它们所操作的对象都是当前线程的 threadLocals 对象的 Entry 数组,因此在不同线程中访问同一个 ThreadLoacl 的 set 和 get 方法,它们对 ThreadLocal 所作的读写操作仅限于各自的线程的内部,这就是为什么 ThreadLocal 可以在多个线程中互不干扰地存储和修改数据。
ThreadLocal 内部结构图
下面通过两张内部结构图能更好的理解 ThreaLocal
- 每个 Thread 线程内部都有一个 ThreadLocalMap
- Map 里面存储线程本地对象 ThreadLocal(key)和线程的变量副本(value)
- Thread 内部的 Map 是由 ThreadLocal 维护,ThreadLocal 负责向 map 获取和设置线程的变量值
- 一个Thread可以有多个ThreadLocal