理解 ThreadLocal

226 阅读3分钟

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);
}
  1. 先拿到当前线程,再使用 getMap 方法拿到当前线程的 threadLocals 变量
  2. 如果 threadLocals 不为空,则将当前 ThreadLocal 作为 key,传入的值作为 value,调用 set 方法插入 threadLocals。
  3. 如果 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();
}
  1. 跟 set 方法差不多,先拿到当前的线程,再使用 getMap 方法拿到当前线程的 threadLocals 变量
  2. 如果 threadLocals 不为空,则将调用 get 方法的 ThreadLocal 作为 key,调用 getEntry 方法找到对应的 Entry。
  3. 如果 threadLocals 为空或者找不到目标 Entry,则调用 setInitialValue 方法进行初始化。
    从 ThreadLoacl 的 set 和 get 方法可以看出,它们所操作的对象都是当前线程的 threadLocals 对象的 Entry 数组,因此在不同线程中访问同一个 ThreadLoacl 的 set 和 get 方法,它们对 ThreadLocal 所作的读写操作仅限于各自的线程的内部,这就是为什么 ThreadLocal 可以在多个线程中互不干扰地存储和修改数据。

ThreadLocal 内部结构图

下面通过两张内部结构图能更好的理解 ThreaLocal ThreadLocal结构 (1).png

ThreadLocal 调用图.png

  • 每个 Thread 线程内部都有一个 ThreadLocalMap
  • Map 里面存储线程本地对象 ThreadLocal(key)和线程的变量副本(value)
  • Thread 内部的 Map 是由 ThreadLocal 维护,ThreadLocal 负责向 map 获取和设置线程的变量值
  • 一个Thread可以有多个ThreadLocal