您的位置:首页 > 移动开发 > Android开发

理解Android中ThreadLocal的工作原理

2016-11-11 18:13 761 查看
一提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()不会访问或修改到其他线程的数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息