ThreadLocal
用于在不同线程中互不干扰的存储并提供数据。
这里不对源码进行深究,只浅显的对实现原理进行了解。
本次涉及到的源码为 Source for Android 27.
ThreadLocal
的实现,需要借助到 ThreadLocalMap
。
需要提前交代的:
在一个 Thread
实例内部,都有一个 threadLocals
成员变量(ThreadLocalMap 类型),而这个 threadLocals
内部又维护了一个 Entry
类型的数组。
而 Entry
是一个 key - value
实体,用于保存 ThreadLocal - Object
键值实体。
先看一段代码:
@Override
protected void onCreate(Bundle savedInstanceState) {
setContentView(R.layout.activity_main3);
ThreadLocal<String> threadLocal = new ThreadLocal<>();
new Thread(() -> {
threadLocal.set("Thread 1");
Log.d("Test", "Thread 1 => "+threadLocal.get());
}).start();
threadLocal.set("Main");
Log.d("Test", "MainThread => "+threadLocal.get());
}
在这段代码里面,生成了一个 ThreadLocal
实例 threadLocal,然后分别在 UI 线程和一个子线程里面去分别进行 set()
和 get()
操作。然后,就会在不同线程里面打印对应的值。
首先看 threadLocal.set()
方法内部:
// ThreadLocal.set()
public void set(T value) {
Thread t = Thread.currentThread();//获得调用 set() 方法所处的线程实例,即 Thread 实例
ThreadLocalMap map = getMap(t);//进一步获得 Thread 实例的 threadLocals(ThreadLocalMap 类型) 成员变量
if (map != null)
map.set(ThreadLocal.this, value);
else
//如果 t 的 threadLocals == null,则新建一个
//新建的时候会将当前 ThreadLocal 的 this 引用传递进去
createMap(t, value);
}
// ThreadLocal.getMap()
ThreadLocalMap getMap(Thread t) {
//每个 Thread 实例都会有一个 threadLocals 成员变量
return t.threadLocals;
}
在某一线程里面调用 threadLocal
的 set(value)
方法,那么 Thread.currentThread()
就会得到当前线程的实例,然后通过该 Thread
实例获取到其内部的成员变量 threadLocals
(ThreadLocalMap
类型),然后将当前 threadLocal
实例作为 key
值,与形参 value
绑定在一起生成一个 Entry
实例(当然这里是通常情况,不考虑重复的 key
而替换原值的情况),并存储到 ThreadLocalMap
内部的 Entry[]
中。
// ThreadLocalMap.set()
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 如果是重复的 key 值,则替换原有的 value 值
// 这就对应着在同一个线程调用两次 threadLocal.set() 情况
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
通过 threadLocal.set(value)
就将 threadLocal
实例与 value
绑定在一起存放在了当前线程(即 Thread
实例)之中(这是从模糊的概念上来说),或者可以说,当前线程根据 threadLocal
实例作为索引,可以存储对应的 value
值。
只要理解了 threadLocal.set(value)
大致的原理,那么对于 threadLocal.get()
方法也容易理解了。
// ThreadLocal
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();
}
在 threadLocal.get()
的时候,就会根据当前线程( Thread 实例)得到对应的 threadLocals
(ThreadLocalMap
类型),再进一步得到 Entry[]
中 key
值为 threadLocal
的 Entry
实例,最后获得 Entry
实例的 value
值。
上述,就是 ThreadLocal
的 set(value)
和 get()
的大致实现,虽然有点绕,但是仔细体会一下,还是容易理解的。
彩蛋部分:
另外,我在自己琢磨到似懂非懂的状态时,就突然产生了一个疑问,为什么 ThreadLocalMap
内部要维护一个 Entry[]
(数组),而不是单个的 Entry 实例,因为明明只要存储 threadLocal-value
的键值对。
后来,突然一下就想明白了,看了下面的代码,也许就能体会到了。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
ThreadLocal<Boolean> threadLocal1 = new ThreadLocal<>();
ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
new Thread(() -> {
threadLocal1.set(true);
threadLocal2.set("Thread 1");
Log.d("Test", "Thread 1 => "+threadLocal1.get());
Log.d("Test", "Thread 1 => "+threadLocal2.get());
}).start();
threadLocal1.set(false);
threadLocal2.set("Main");
Log.d("Test", "MainThread => "+threadLocal1.get());
Log.d("Test", "MainThread => "+threadLocal2.get());
}
对于每一个线程(即 Thread
实例)来说,可能遇到需要维护多个不同的 threadLocal-value
的情况(即会产生多个不同的 Entry
实例),因此,就需要一个 Entry[]
来保存多个 Entry
实例。
最后,还需要说明的一点是,ThreadLocal
的作用是为不同的线程存储对应的对象(实际上是对象的引用,想想 String
类型),如果在两个线程中存储的都是同一个对象的引用,那么在两个线程中得到也必然会是同一对象的引用,这点是需要注意的。
public class Test {
int anInt;
public static void main(String[] args) {
Test t = new Test();
t.anInt = 3;
ThreadLocal<Test> local = new ThreadLocal<>();
local.set(t);
local.get().t(10);
new Thread(() -> {
local.set(t);
System.out.println(local.get().anInt);
}).start();
System.out.println(local.get().anInt);
}
public int t(int v) {
anInt = v;
return anInt;
}
}
打印结果:
10
10
最后,还是总结一下,ThreadLocal
的大致实现原理:
在某一线程 thread1
中调用 threadLocal1.set(value)
时,实际上是把该 threadLocal1
与 value
以健值对的形式存储在 thread1
中(更进一步的说是一个健值对数组中,因为 thread1
可能对应多个 ThreadLocal
对象)
然后调用 threadLocal1.get()
方法时,实际上会先得到调用该方法时的线程(通过 Thread.currentThread()
方法实现)thread1
,然后通过 thread1
且以 threadLocal1
自身为 key
从健值对数组中得到 threadLocal1
对应的 value
。