理解Android中ThreadLocal的工作原理
阅读原文时间:2021年04月20日阅读:1

一提Android中的消息机制,我们很容易就想到Handler,确实,Android中的异步消息处理机制,主要都依赖于Handler机制(不懂的可以看深入理解Android中的Handler机制 这篇文章),而Handler机制的实现其实和ThreadLocal密不可分。

ThreadLocal

Implements a thread-local storage, that is, a variable for which each thread has its own value. All threads share the same {@code ThreadLocal} object, but each sees a different value when accessing it, and changes made by one thread do not affect the other threads. The implementation supports {@code null} values.

以上是官方文档对ThreadLocal的解释,从这里我们可以看出,ThreadLocal相当于一个容器,存储着每个线程的数据,且所有线程都共享这一个ThreadLocal变量,但每个线程访问该对象时会获得不同的数据,而且就算修改这些数据也不会影响到其他线程,即所有线程互不干扰。因为这个特性,ThreadLocal在Java中也常用于处理多线程,也就是说,ThreadLocal和线程同步机制一样,都是为了解决多线程访问同一变量造成的访问冲突问题,不同的的是,

  • 同步机制是通过锁机制牺牲时间来达到多线程安全访问的目的;
  • ThreadLocal为每个线程提供独立的变量副本,牺牲空间保证每个线程访问相同变量时可以得到自己专属的变量,达到多线程安全访问的目的。

具体如何实现的呢?我们从Android中的Handler机制进行讲解。
在Handler机制中,我们需要新建一个线程,而在每个Thread中,都会包含一个ThreadLocal.Values 类型的localValues变量,表示该线程映射到ThreadLocal中的值。

ThreadLocal.Values

首先来看一下它的一个和消息机制相关的构造函数。

/** Size must always be a power of 2. */
private static final int INITIAL_SIZE = 16;

/** Placeholder for deleted entries. */
private static final Object TOMBSTONE = new Object();

/**
 * Map entries. Contains alternating keys (ThreadLocal) and values.
 * The length is always a power of 2.
 */
private Object[] table;

/** Used to turn hashes into indices. */
private int mask;

/** Number of live entries. */
private int size;

/** Number of tombstones. */
private int tombstones;

/** Maximum number of live entries and tombstones. */
private int maximumLoad;

/** Points to the next cell to clean up. */
private int clean;

Values() {
    initializeTable(INITIAL_SIZE);
    this.size = 0;
    this.tombstones = 0;
}

private void initializeTable(int capacity) {
    this.table = new Object[capacity * 2];
    this.mask = table.length - 1;
    this.clean = 0;
    this.maximumLoad = capacity * 2 / 3; // 2/3
}

这里主要是对几个变量进行初始化初始化。包括用于存储线程本身一些数据的Object数组table,该数组也存储了线程间共享的ThreadLocal对象和自身相关的Looper 对象,还有用于将哈希值转为指数的掩码。

记住该Values类后我们来看ThreadLocal是如何在消息机制中发挥作用的。

在Handler机制中,

  • Handler用于处理子线程耗时操作结束后的反馈,一般用于从子线程切换回主线程并更新UI。

  • MessageQueue用于存储消息。

  • Looper用于处理消息,以无限循环的形式查找MessageQueue中是否有消息。
    所以每个线程的运行都需要Looper的存在,不然无法处理消息(不懂的先完看深入理解Android中的Handler机制 这篇文章)。
    所以先讲下调用Looper的构建。通常我们都是通过Looper.prepare()来构建looper实例的,但是该方法除了实例一个looper并创建一个MessageQueue、指定其mThread为当前线程外,就是将该Looper对象存到ThreadLocal中。

    public static void prepare() {
    prepare(true);
    }

    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的set方法,如下:

public void set(T value) {
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values == null) {
        values = initializeValues(currentThread);
    }
    values.put(this, value);
}

Values values(Thread current) {
    return current.localValues;
}

Values initializeValues(Thread current) {
    return current.localValues = new Values();
}

这里 values(currentThread) 方法返回的就是我们前面提到的ThreadLocal.Values对象,也就是Thread类中的localValue值,如果是null就进行初始化,初始化执行的就是我们前面提到的构造方法。初始化成功后便将该ThreadLocal对象和该looper对象(即参数value)存储到该values对象中,具体实现如下:

void put(ThreadLocal<?> key, Object value) {
    // Cleans up after garbage-collected thread locals.
    cleanUp();

    // Keep track of first tombstone. That's where we want to go back
    // and add an entry if necessary.
    int firstTombstone = -1;

    for (int index = key.hash & mask;; index = next(index)) {
        Object k = table[index];

        if (k == key.reference) {
            // Replace existing entry.
            table[index + 1] = value;
            return;
        }

        if (k == null) {
            if (firstTombstone == -1) {
                // Fill in null slot.
                table[index] = key.reference;
                table[index + 1] = value;
                size++;
                return;
            }

            // Go back and replace first tombstone.
            table[firstTombstone] = key.reference;
            table[firstTombstone + 1] = value;
            tombstones--;
            size++;
            return;
        }

        // Remember first tombstone.
        if (firstTombstone == -1 && k == TOMBSTONE) {
            firstTombstone = index;
        }
    }
}

private int next(int index) {
    return (index + 2) & mask;
}

从上面代码可以看到,该方法其实就是把传进来的ThreadLocal对象和looper对象存到它的table数组中,可以看到,这里通过计算该threadLocal的哈希值key.hash与掩码值mask 的与运算结果来判断table中是否存在该threadLocal对象的引用reference ,若存在则将该looper对象放在该引用在table数组中的位置的后一个位置,即table[index + 1];若不存在则将该threadLocal对象的引用存到table数组的[index]位置上,而looper对象同样存在其后一个位置。也就是说,looper的值都会存在threadLocal引用的后一个位置。
到这里Looper构造并准备完毕。
然后就应该构造Handler获取looper了。在创建handler时,若参数中没有指定looper,会通过mLooper = Looper.myLooper(); 为自身指定一个looper对象。而myLooper实际上就是返回threadLocal.get()而已。

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

那么具体是如何获得当前线程相关的looper的呢?

public T get() {
    // Optimized for the fast path.
    Thread currentThread = Thread.currentThread();
    Values values = values(currentThread);
    if (values != null) {
        Object[] table = values.table;
        int index = hash & values.mask;
        if (this.reference == table[index]) {
            return (T) table[index + 1];
        }
    } else {
        values = initializeValues(currentThread);
    }

    return (T) values.getAfterMiss(this);
}

Values values(Thread current) {
    return current.localValues;
}

Values initializeValues(Thread current) {
    return current.localValues = new Values();
}

可以看到,这里依然是通过当前线程自身的localValues这个值内部的table数组来获得相关联的looper对象,如果是null,则重新初始化该Values对象,如果不为null则返回table数组中该ThreadLocal对象的引用所在位置的下一个值,也就是我们前面set进去looper啦。

综上,我们可以知道,在handler机制中,之所以每个线程能够获得自身的looper,而不影响其他线程,是因为通过操作每个线程自身的localValues对象内部的table数组实现的,从而达到不同线程访问同一个threadLocal的get()和set()不会访问或修改到其他线程的数据。