理解Android中ThreadLocal的工作原理

来源:互联网 发布:mysql 什么情况锁表 编辑:程序博客网 时间:2024/06/06 14:09

一提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()不会访问或修改到其他线程的数据。

0 0
原创粉丝点击