Android 消息机制深入源码分析 [ 一 ]
Android 消息机制之 ThreadLocal 深入源码分析 [ 二 ]
Android 消息机制之 Looper 深入源码分析 [ 三 ]
Android 消息机制之 Message 与消息对象池的深入源码分析 [ 四 ]
Android 消息机制之 MessageQueue 深入源码分析 [ 五 ]
Android 消息机制之初识Handler [ 六 ]
Android 消息机制之 Handler 发送消息的深入源码分析 [ 七 ]
Android 消息机制之 MessageQueue.next() 消息取出的深入源码分析 [ 八 ]
Android 消息机制之消息的其他处理深入源码分析 [ 九 ]
Android 消息机制总结 [ 十 ]
接着上一章, 从本章开始源码进行分析 Android 的消息机制.
1. ThreadLocal 是什么.
- ThreadLocal 是一个线程内部的数据存储类, 通过它可以在指定的线程中存储数据, 数据存储以后, 只有在指定线程中可以获取到存储的数据, 对于其他线程来说则无法获取到数据.
2. 什么情况下使用 ThreadLocal
当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候, 就可以考虑使用 ThreadLocal, 比如对于 Handler 来说, 它需要获取当前线程的 Looper, 很显然 Looper 的作用域就是线程并且不同线程具有不同的 Looper, 这个时候通过 ThreadLocal 就可以轻松实现 Looper 在线程中的获取.
-
另一个场景是在复杂逻辑下的对象传递, 比如监听器的传递, 有时候一个线程中的任务过于繁杂, 这可能表现为函数调用栈比较深以及代码入口的多样性. 在这种情况下, 又需要监听器能够贯穿整个线程的执行过程, 这个时候就可以采用 ThreadLocal. 采用 ThreadLocal 可以让监听器作为线程内的全局对象而存在, 在线程内只要通过 get 方法就可以获取到监听器. 如果不采用 ThreadLocal, 那么可能会有下面两种方式.
- 将监听器通过参数的形式在函数调用栈中进行传递, 但是当函数调用栈很深的时候, 通过函数传递监听器对象几乎是不可能接受的, 会让程序看起来很 Low.
- 将监听器作为静态变量供线程访问. 这个倒是可以接受, 但是这种情况是不具有可扩展性的. 比如两个线程在执行, 那么就需要提供两个静态的监听器对象, 如果有 10 个线程在并发执行呢, 100 个呢 ?
- 而采用 ThreadLocal, 每个监听器对象都在自己的线程内部存储, 根本不会有第二种情况出现.
下面使用一个例子来简单的使用一下 ThreadLocal.
3. 例
public class MainActivity extends AppCompatActivity {
final String TAG = "MainActivity";
private ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBooleanThreadLocal.set(true);
Log.d(TAG,"[ Thread#main ] mBooleanThreadLocal =" + mBooleanThreadLocal.get());
new Thread("Thread#1"){
@Override
public void run() {
mBooleanThreadLocal.set(false);
Log.d(TAG,"[ Thread#1 ] mBooleanThreadLocal =" + mBooleanThreadLocal.get());
}
}.start();
new Thread("Thread#2"){
@Override
public void run() {
// mBooleanThreadLocal.set(false);
Log.d(TAG,"[ Thread#2 ] mBooleanThreadLocal =" + mBooleanThreadLocal.get());
}
}.start();
}
}
输出结果
D/MainActivity: [ Thread#main ] mBooleanThreadLocal =true
D/MainActivity: [ Thread#1 ] mBooleanThreadLocal =false
D/MainActivity: [ Thread#2 ] mBooleanThreadLocal =null
- 在主线程中设置 mBooleanThreadLocal 为 true, 在子线程 Thread#1 中设置 mBooleanThreadLocal 为 false. 在子线程 Thread#2 不设置. 然后在三个线程中分别通过 get 方法获取 mBooleanThreadLocal 的值, 根据上面对 ThreadLocal 的描述, 这个时候, 主线程中应该是 true,子1中应该是 false, 子2中没有设置应该是 null.
- 虽然他们在不同的线程访问的是同一个 ThreadLocal 对象, 但是获取到的值却是不同的. 不同线程访问 ThreadLocal 的 get 方法, ThreadLocal 内部会从各自线程中取出一个数组, 然后再从数组中根据当前线程 ThreadLocal 的索引去查找对应的 value 值, 显然, 不同线程中的数据是不同的, 在哪个线程中设置的值, 那么这个值就存储在哪个线程中. 这就是为什么通过 ThreadLocal 可以在不同线程中维护一套数据副本, 并且彼此互不干扰
4. ThreadLocal 内部实现
关键代码如下:
class ThreadLocal<T>{
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static int nextHashCode() {
//自增
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
//分析 1
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//分析 2
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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
- 分析 1
- 分析 2
5. ThreadLocalMap 的内部实现
关键代码 ThreadLocal.java 298行
class ThreadLocal{
...
static class ThreadLocalMap{
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private void set(ThreadLocal<?> key, Object value) {
//一起贴出来代码会过长, 不方便阅读, 所以下面会单独拿出来看.
}
private Entry getEntry(ThreadLocal<?> key) {
//一起贴出来代码会过长, 不方便阅读, 所以下面会单独拿出来看.
}
}
}
5.1 现在开始看 ThreadLocalMap.set()
class ThreadLocal{
...
static class ThreadLocalMap{
...
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//分析 1
int index = key.threadLocalHashCode & (len-1);
for (Entry e = tab[index]; e != null; e = tab[index = nextIndex(index, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, index);
return;
}
}
//分析 2
tab[index] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(index, sz) && sz >= threshold){
rehash();
}
}
//分析 3
private void rehash() {
expungeStaleEntries();
if (size >= threshold - threshold / 4){
resize();
}
}
}
}
- 分析 1
- 分析 2
- 分析 3
- set 总结
6. ThreadLocal 总结
- 每个线程都持有一个
ThreadLocalMap
的引用 (代码在Thread.java
190行),ThreadLocalMap
中又有一个Entry
类型叫table
数组, 而Entry
又是以键值对的形式来存储数据,key
为ThreadLocal
类型. 所以, 同一线程可以有多个ThreadLocal
, 但是对于同一线程不同的ThreadLocal
来说, 它们共享的同一个table
数组, 只是在table
中的索引不同. -
Entry
的key
是弱引用, 当空间不足的时候, 会清理未被引用的Entry
对象. 所以会有过期的Entry
, 也就是Entry
不为空, 但是Entry.get()
- 对于某一
ThreadLocal
来说, 它的索引值是确定的, 在不同线程之间访问时, 访问的是不同的table
数组的同一位置, 只不过这个不同线程之间的table
是独立的.