一、ThreadLocal 是什么
ThreadLocal 是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据
可以理解成线程本地变量或线程本地存储,ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量
二、使用方法
示例代码:
public class Page47 {
ThreadLocal<Integer> int_local = new ThreadLocal<>();
ThreadLocal<String> str_local = new ThreadLocal<>();
private void set() {
int_local.set(120);
int_local.set(136);
str_local.set("Hello");
}
private void change() {
int_local.set(150);
str_local.set("World");
}
private int getInt() {
return int_local.get();
}
private String getStr() {
return str_local.get();
}
public static void main(String[] args) throws InterruptedException {
Page47 page47 = new Page47();
// 主线程中设置值
page47.set();
// 打印线程名称:main
System.out.println("当前线程:" + Thread.currentThread().getName());
// 主线程中获取 ThreadLocal<Integer> 类型变量 int_local 中存的值
System.out.println(page47.getInt());
// 主线程中获取 ThreadLocal<String> 类型变量 str_local 中存的值
System.out.println(page47.getStr());
System.out.println("=======");
Thread thread = new Thread() {
public void run() {
// 子线程中设置值
page47.set();
System.out.println("当前线程:" + Thread.currentThread().getName());
// 子线程中获取 ThreadLocal<Integer> 类型变量 int_local 中存的值
System.out.println(page47.getInt());
// 子线程中获取 ThreadLocal<String> 类型变量 str_local 中存的值
System.out.println(page47.getStr());
// 子线程中改变 ThreadLocal 类型变量 int_local 和 str_local 对应的值
page47.change();
System.out.println("子线程修改值后:");
// 子线程中获取 ThreadLocal<Integer> 类型变量 int_local 中存的值
System.out.println(page47.getInt());
// 子线程中获取 ThreadLocal<String> 类型变量 str_local 中存的值
System.out.println(page47.getStr());
};
};
thread.start();
thread.join();
System.out.println("=======");
// 打印线程名称
System.out.println("当前线程:" + Thread.currentThread().getName());
// 主线程中获取 ThreadLocal<Integer> 类型变量 int_local 中存的值
System.out.println(page47.getInt());
// 主线程中获取 ThreadLocal<String> 类型变量 str_local 中存的值
System.out.println(page47.getStr());
}
}
控制台打印结果:
从控制台打印结果可以看出:不同线程对 ThreadLocal 类型变量进行修改,只在当前线程有效,不影响其它线程
2.1 问题一:ThreadLocal 怎么存的
ThreadLocal#set():
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程对应的存储(ThreadLocalMap)
// 关键一:getMap() 返回值为 ThreadLocalMap
ThreadLocalMap map = getMap(t);
// map 存储不为空
if (map != null)
// 关键二:ThreadLocalMap#set()
// key 为当前线程的 ThreadLocal
map.set(this, value);
else // map 存储为空
// 创建 ThreadLocalMap
createMap(t, value);
}
关键一:getMap()
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
由此可知,Thread 的成员变量 threadLocals 是 ThreadLocalMap 类型
关键二:ThreadLocalMap#set()
private void set(ThreadLocal<?> key, Object value) {
// Entry 是 ThreadLocalMap 的静态内部类
// Entry 构建函数:Entry(ThreadLocal<?> k, Object v),key 是 ThreadLocal,值是变量副本
// Entry 组成的数组 tab 用来存储线程对应的变量副本
Entry[] tab = table;
int len = tab.length;
// 数组下标的结果和 ThreadLocal 有关,ThreadLocal 相同,数组下标就相同
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();
}
示例代码中的 set() 方法:
public class Page47 {
ThreadLocal<Integer> int_local = new ThreadLocal<>();
ThreadLocal<String> str_local = new ThreadLocal<>();
private void set() {
// 对 int_local 进行 set 操作
// int_local 先 set(120)
int_local.set(120);
// int_local 再 set(136)
int_local.set(136);
// 对 str_local 进行 set 操作
str_local.set("Hello");
}
...
}
对示例代码的三行代码进行 debug,查看数组 tab 的状态
1.根据关键二:ThreadLocalMap#set() 可知,数组下标的结果和 ThreadLocal 有关,ThreadLocal 相同,数组下标就相同,则对 ThreadLocal int_local 进行 set 操作,先存 120,之后的 136 会覆盖 120
2.因为示例代码中 str_local 和 int_local 是两个 ThreadLocal 对象,因此数组下标会不同,会在数组 tab 中按对应下标存储
代码 int_local.set(120);
执行之前,ThreadLocalMap 中数组 tab 的状态:
如图所示 480 行:准备创建一个新的 Entry 对象,key 是 ThreadLocal,value 是 120再执行一步后,数组 tab 的状态:
如图所示,数组 tab 下标为 3 的值为 120代码执行 int_local.set(136);
执行之前,数组 tab 的状态:
如图 470 行,因为操作的还是 ThreadLocal int_local,所以此时 key 相同,那么将会把之前存储的 120 更新为即将存储的 136再执行一步后,数组 tab 的状态:
如图所示,数组 tab 下标为 3 的 120,此时变成了 136代码 str_local.set("Hello");
执行之后,数组 tab 的状态:
如图所示,数组 tab 下标为 10 的值为 Hello,下标为 3 的值为 136
2.2 问题二:ThreadLocal 怎么取的
ThreadLocal#get():
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程对应的存储(ThreadLocalMap)
ThreadLocalMap map = getMap(t);
// map 存储不为空
if (map != null) {
// this 指的是 ThreadLocal,不是当前线程 t
ThreadLocalMap.Entry e = map.getEntry(this);
// Entry 不为空
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
// 返回 value
return result;
}
}
// map 为空,返回 setInitialValue() 的返回值
// 关键三:ThreadLocal#setInitialValue()
return setInitialValue();
}
关键三:
ThreadLocal#setInitialValue():
private T setInitialValue() {
// 关键四:initialValue() 默认返回 null
T value = initialValue();
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程对应的存储(ThreadLocalMap)
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
// 返回 initialValue() 的返回值
return value;
}
关键四:initialValue() 默认返回 null
ThreadLocal#initialValue():
protected T initialValue() {
// 如果没有重写当前方法,则默认返回 null
return null;
}
三、应用场景
当某些数据是以线程为作用域并且不同线程具有不同的数据副本时,就可以考虑采用 ThreadLocal
Android 中 Handler 对应的 Looper 就使用到了 ThreadLocal,Looper 以线程为作用域,且不同线程具有不同的 Looper