ThreadLocal 是什么?
要学习一个新东西至少要知道它是什么?这点应该是确定的。那么 ThreadLocal 到底是什么呢?其实Thread(线程)、Local(本地)这两个单词都不算太难,在平时也是屡见不鲜,ThreadLocal 很容易让人望文生义,想当然地认为是一个“本地线程”。其实 ThreadLocal 并不是一个 Thread,而是 Thread 的局部变量。那么它的作用到底是什么?先来看看JDK(13)文档的解释:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
谷歌翻译如下:
此类提供线程局部变量。 这些变量与普通变量不同,因为每个访问一个线程(通过其get或set方法)的线程都有其自己的,独立初始化的变量副本。 ThreadLocal 实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或交易ID)。
虽然翻译的不是那么准确,但是还是能看出来一些重点
ThreadLocal 提供一个线 程内的局部变量,它是每个线程独有的,与其它线程互不干扰。
如何使用 ThreadLocal?
先上一段代码,感受一下
new Thread(new Runnable_1()).start();
new Thread(new Runnable_2()).start();
ThreadLocal<Integer> mLocal = new ThreadLocal<>();
class Runnable_1 implements Runnable {
@Override
public void run() {
Integer value1 = mLocal.get(); //----------①
Log.e("ThreadLocal", "value1==" + value1);
mLocal.set(10); //----------②
Integer value2 = mLocal.get();
Log.e("ThreadLocal", "value2==" + value2);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
mLocal.remove(); //----------③
Integer value3 = mLocal.get();
Log.e("ThreadLocal", "value3==" + value3);
}
}
class Runnable_2 implements Runnable {
@Override
public void run() {
Integer value4 = mLocal.get();
Log.e("ThreadLocal", "value4==" + value4);
}
}
日志如下:
2019-12-10 10:51:40.733 13975-14031/com.author.pf.androidproject E/ThreadLocal: value1==null
2019-12-10 10:51:40.733 13975-14031/com.author.pf.androidproject E/ThreadLocal: value2==10
2019-12-10 10:51:40.733 13975-14032/com.author.pf.androidproject E/ThreadLocal: value4==null
2019-12-10 10:51:43.734 13975-14031/com.author.pf.androidproject E/ThreadLocal: value3==null
咦~ 结果好像有点不太好解释,为什么这么说呢?在 Runnable_1 和 Runnable_2 中进行操作的 ThreadLocal 明明是一个对象,那么为什么在 value2 已经打印出 10 的时候 value4 输出结果仍为 null?针对一个个的疑问最好的办法就是从源码中找出答案。
从 JDK 中可以查看 ThreadLocal 所涉及的方法并不多,下面是基于JDK 13 文档的截图:
ThreadLocal#get
ThreadLocal 中主要的方法在上面示例代码中已经体现出来的,接下来首先分析 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();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal#get 方法代码并不难理解
- 获取当前线程实例 t
- 通过 ThreadLocal#getMap 获取 ThreadLocalMap 对象实例 map
- 对 map 进行非空判断,若不为空执行 if 语句里面的内容,反之执行 setInitialValue 方法
情况1:当 map==null ,执行 setInitialValue 方法,那么具体看下 setInitialValue 方法:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal#setInitialValue
- 执行 initialValue 方法,即 value 赋值为 null
- 获取当前线程实例 t
- 执行 getMap 方法返回 ThreadLocalMap 实例 map
- 由于 map==null,因此会执行 createMap 方法,createMap 顾名思义即就是生成 ThreadLocalMap 的实例,并且赋值给当前线程里面的 ThreadLocalMap 对象实例 threadLocals。这里的 this 代表的是 ThreadLocal 实例 firstValue = null,这也就解释了最上面示例中 value1=null 的原因
情况2:当 map!=null ,执行 if 语句中的代码
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
ThreadLocalMap
单独说说这个高频出现的 ThreadLocalMap, ThreadLocalMap 内部维持一个哈希表,提到哈希表分析过 HashMap 源码的童鞋应该不会陌生,这里就简单的梳理一下。存储数据 key 从上面的源码中也可以清楚地看到应该是 ThreadLocal 实例,value 即为我们执行 set 方法时候设置的值。
- ThreadLocalMap中其实是维护了一张哈希表,这个表里面就是 Entry 对象
- 每一个Entry对象存放了key和value值
通过这些再来分析上述 if 语句中的代码就显得非常的简单,通过 getEntry 方法获取到当前线程中 ThreadLocalMap 里面的 Entry 对象,由于 Entry 对象对象是以 key-value 键值对形式存储数据,因此直接返回查询结果即 e.value
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);
}
ThreadLocal#set 方法中的前两句代码在上面已经分析过了,这里就跳过不再赘述,继续向下分析会对实例 map 进行非空判断,map=null 的情况也分析过了,重点是 map.set(this, value) 方法:
ThreadLocalMap#set
private void set(ThreadLocal<?> key, Object value) {
//将数组 table 赋值给 tab
Entry[] tab = table;
//数组长度
int len = tab.length;
//获取将被存放在数组中的下标
int i = key.threadLocalHashCode & (len-1);
//若在数组中当前索引 i 下面并未存储过值,即e = tab[i]=null 则不会进入 for 循环
//否则,会进入循环体
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
//获取当前下标 i 处 Entry 对象的 key 值==>ThreadLocal 的实例 k
ThreadLocal<?> k = e.get();
//若 k 值与当前传进来的 key 值相等则直接将原来的 e.value 更新为
//当前传递进来的 value
//辅助理解:在 Runnable_1 中已经设置 mLocal.set(10),然后再次设置
//mLocal.set(20) 那么 20 则会覆盖之前的值 10
if (k == key) {
e.value = value;
return;
}
//k==null 说明 key 已经被系统回收,则会执行方法 replaceStaleEntry
//将新的 key 和 value 重新放进去
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
//若以上两种情况都不满足,则会继续向后移动一个位置继续做判断,直到
//k==null 或者同一个 ThreadLocal 的引用时才会插入数据
}
//若 tab[i]=null 则未执行上述 for 循环,直接执行这里,也就是直接创建新的
//Entry 对象并存储到对应的下标 i 处
tab[i] = new Entry(key, value);
//执行 +1 操作
int sz = ++size;
// 判断是否需要进行扩容操作(可类比 HashMap 源码进行理解)
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
使用场景
当需要将某个变量需要与线程相关联,并且线程后续还会用到这个变量当时存储的值进行其他操作,使用 ThreadLocal 会很方便
来源:CSDN
作者:pengfei0828
链接:https://blog.csdn.net/pengfei_0828/article/details/103469928