一、概述
ThreadLocal
是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
在日常开发中用到ThreadLocal
的地方较少,但在某些特殊的场景下,通过ThreadLocal
可以轻松地实现一些看起来很复杂的功能。下面例举两个场景:
- 一般来说,当某些数据是以线程为作用域且不同线程具有不同的数据副本时,可以考虑使用
ThreadLocal
。比如对于Handler
而言,它需要获取当前线程的Looper
,这个时候通过ThreadLocal
就可以轻松实现Looper
在线程中的存取。- 复杂逻辑下的对象传递,比如监听器的传递,有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码的多样性等,这是就可以采用
ThreadLocal
来让监听器作为线程内的全局对象。
二、示例
上述关于ThreadLocal
的概念可能有点抽象,下面将通过一个一个例子来演示ThreadLocal
的真正含义:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
//定义ThreadLocal对象
private ThreadLocal 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() {
Log.d(TAG, "[Thread#2]mBooleanThreadLocal=" + mBooleanThreadLocal.get());
}
}.start();
}
}
在上面的代码中,在主线程中设置mBooleanThreadLocal
的值为true
,在子线程1中设置mBooleanThreadLocal
值为false
,子线程2中不设置mBooleanThreadLocal
的值。然后在3个线程中分别通过get
方法获取值并打印出来。根据前面的描述,期望的打印结果应该是:主线程为true
,子线程1为false
,子线程2位null
,因为子线程2中没有设置值。实际打印结果如下:
与预期结果一致。虽然在不同线程中访问的是同一个mBooleanThreadLocal
对象,但是她们通过ThreadLocal
获取到的值是不一样的,这就是ThreadLocal
的奇妙之处。结合这个例子然后再看一遍前面对ThreadLocal
的两个使用场景的理论分析,应该能够较好的理解ThreadLocal
的使用方法了。
三、工作原理
ThreadLocal
是一个泛型类,它的定义为public class ThreadLocal
,只要弄清楚了ThreadLocal
的get
和set
方法就可以明白它的工作原理了。
首先看ThreadLocal
的set
方法,如下所示:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
在上面的set
方法中,首先会通过getMap
方法来获取当前线程中的ThreadLocal
数据,如果返回的ThreadLocalMap
对象不为空,则调用set
方法添加数据,否则就创建一个新对象并把值添加进去。ThreadLocalMap
中定义了一个private Entry[] table;
来存储数据,我们可以通过set
方法将数据添加到table
数组中,具体方法如下所示:
private void set(ThreadLocal> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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();
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
的值的存储索引计算方法为i = key.threadLocalHashCode & (len-1);
。
上面分析了ThreadLocal
的set
方法,下面来看get
方法:
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
方法逻辑也比较清晰,同样是先取出当前线程的ThreadLocalMap
对象,如果这个对象为null
就返回初始值,这个初始值由ThreadLocal
的initialValue()
方法来描述,默认情况下为null
,当然也可以重写这个方法,它的默认实现如下所示:
protected T initialValue() {
return null;
}
如果ThreadLocalMap
对象不为null
,那么就取出table
数组并找到相应的值返回回去。
从ThreadLocal
的set
和get
方法可以看出,它们所操作的对象都是当前线程的ThreadLocalMap
对象的table
数组,因此在不同线程中访问同一个ThreadLocal
的set
和get
方法,它们对ThreadLocal
所做的读/写操作仅限于各线程的内部,这就是为什么ThreadLocal
可以在多个线程中互不干扰地存储和修改数据的原因所在。
注:上述源码版本为API26,不同版本源码可能会有些许差异。