记录:389
场景:使用InheritableThreadLocal和ThreadLocal,实现一个变量var,在线程A执行的多个环节中传递变量var。比如从HttpServletRequest的请求头获取的token值存放到ThreadLocal,那么在Controller层、Service层、Dao层以及相关环节中均可以从ThreadLocal获取token值。
版本:JDK 1.8,SpringBoot 2.6.3
1.实现ThreadLocal工具类
1.1工具类
public class ThreadLocalTool {
private static InheritableThreadLocal<Map<String, Object>> reqMap = new InheritableThreadLocal<Map<String, Object>>() {
// initialValue是ThreadLocal初始化方法
protected Map<String, Object> initialValue() {
return new HashMap<>();
}
// parentValue值存入childValue一并返回
protected Map<String, Object> childValue(Map<String, Object> parentValue) {
Map<String, Object> childValue = this.initialValue();
childValue.putAll(parentValue);
return childValue;
}
};
// 获取当前线程值,reqMap.get()是ThreadLocal的get方法
public static Map<String, Object> getCurrentValue() {
return reqMap.get();
}
// 获取当前线程指定key的值
public static Object getCurrentValueByKey(String key) {
return reqMap.get().get(key);
}
// 设置当前线程值
public static void setCurrentValue(String key, Object obj) {
reqMap.get().put(key, obj);
}
// 清空当前线程值
public static void clearValue() {
reqMap.get().clear();
}
}
1.2ThreadLocal使用ThreadLocalMap为每个线程维护一份数据
(1)初始化创建对象InheritableThreadLocal,此对象继承ThreadLocal。
(2)在InheritableThreadLocal的initialValue方法中,传入给ThreadLocal一个已初始的对象,自定义变量就存储在这个对象中。
protected Map<String, Object> initialValue() {
return new HashMap<>();
}
(3)在ThreadLocal中的setInitialValue方法中,使用ThreadLocalMap为每个线程维护一份数据。
ThreadLocalMap的key值是当前线程,value是传入的对象。
类:java.lang.ThreadLocal
方法:
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;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
1.3存储变量逻辑
(1)工具类方法,ThreadLocalTool.setCurrentValue("中国", "china")。
(2)底层调用ThreadLocal的get方法获取当前线程变量Map<String, Object>对象。
自定义代码入口:
public static void setCurrentValue(String key, Object obj) {
reqMap.get().put(key, obj);
}
底层ThreadLocal代码入口:
类:java.lang.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();
}
解析:为根据当前线程获取为该线程存储的变量。
调用Map<String, Object>的put(key, obj)存入值。
1.4读取变量逻辑
(1)工具类方法,ThreadLocalTool.getCurrentValueByKey("中国")。
(2)底层调用ThreadLocal的get方法获取当前线程变量Map<String, Object>对象。
自定义代码入口:
public static Object getCurrentValueByKey(String key) {
return reqMap.get().get(key);
}
底层ThreadLocal代码入口:
类:java.lang.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();
}
解析:为根据当前线程获取为该线程存储的变量。
(3)调用Map<String, Object>的get(key)存入值。
2.关于ThreadLocal本质
ThreadLocal为每个线程存储一份变量的核心,就是ThreadLocal内部维护了ThreadLocalMap。ThreadLocalMap类是一个(Key,Value)键值对类,以每个线程对象为key,以传入给为ThreadLocal的对象Value。达到一个线程对应一个变量效果。
3.验证ThreadLocal示例
3.1工具类ThreadLocalTool
public class ThreadLocalTool {
private static InheritableThreadLocal<Map<String, Object>> reqMap = new InheritableThreadLocal<Map<String, Object>>() {
// initialValue是ThreadLocal初始化方法
protected Map<String, Object> initialValue() {
return new HashMap<>();
}
// parentValue值存入childValue一并返回
protected Map<String, Object> childValue(Map<String, Object> parentValue) {
Map<String, Object> childValue = this.initialValue();
childValue.putAll(parentValue);
return childValue;
}
};
// 获取当前线程值,reqMap.get()是ThreadLocal的get方法
public static Map<String, Object> getCurrentValue() {
return reqMap.get();
}
// 获取当前线程指定key的值
public static Object getCurrentValueByKey(String key) {
return reqMap.get().get(key);
}
// 设置当前线程值
public static void setCurrentValue(String key, Object obj) {
reqMap.get().put(key, obj);
}
// 清空当前线程值
public static void clearValue() {
reqMap.get().clear();
}
}
3.2线程HangzhouRunnable
public class HangzhouRunnable implements Runnable {
private long millis;
public HangzhouRunnable(long millis) {
this.millis = millis;
}
@Override
public void run() {
System.out.println("HangzhouThread加入数据.");
ThreadLocalTool.setCurrentValue("杭州", "hangzhou");
ThreadLocalTool.setCurrentValue("杭州-西湖", "hangzhou-xihu");
this.sleep();
System.out.println("HangzhouThread遍历数据(休息3秒后),线程名称: "+Thread.currentThread().getName());
Map<String, Object> map = ThreadLocalTool.getCurrentValue();
map.forEach((key, value) -> {
System.out.println("key = " + key + ",value = " + value);
});
}
public void sleep() {
try {
Thread.sleep(this.millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.3线程SuzhouRunnable
public class SuzhouRunnable implements Runnable {
private long millis;
public SuzhouRunnable(long millis){
this.millis=millis;
}
@Override
public void run() {
System.out.println("SuzhouThread加入数据.");
ThreadLocalTool.setCurrentValue("苏州","suzhou");
ThreadLocalTool.setCurrentValue("苏州-昆山","suzhou-kunshan");
this.sleep();
System.out.println("SuzhouThread遍历数据(休息2秒后),线程名称: "+Thread.currentThread().getName());
Map<String, Object> map= ThreadLocalTool.getCurrentValue();
map.forEach((key,value)->{
System.out.println("key = " + key + ",value = " + value);
});
}
public void sleep(){
try {
Thread.sleep(this.millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.4主线类
public class MainDemo {
public static void main(String[] args) {
System.out.println("MainDemo加入数据.");
ThreadLocalTool.setCurrentValue("中国", "china");
Thread hangzhouThread = new Thread(new HangzhouRunnable(3000));
hangzhouThread.start();
Thread suzhouThread = new Thread(new SuzhouRunnable(2000));
suzhouThread.start();
sleep(5000);
Map<String, Object> main = ThreadLocalTool.getCurrentValue();
System.out.println("MainDemo遍历数据(休息5秒后),线程名称: "+Thread.currentThread().getName());
main.forEach((key, value) -> {
System.out.println("key = " + key + ",value = " + value);
});
}
public static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.5测试结论
(1)主线程变量可以传递到子线程。
(2)子线程只能读取子线程内容。
(3)执行后打印
MainDemo加入数据.
HangzhouThread加入数据.
SuzhouThread加入数据.
SuzhouThread遍历数据(休息2秒后),线程名称: Thread-1
key = 苏州-昆山,value = suzhou-kunshan
key = 中国,value = china
key = 苏州,value = suzhou
HangzhouThread遍历数据(休息3秒后),线程名称: Thread-0
key = 中国,value = china
key = 杭州,value = hangzhou
key = 杭州-西湖,value = hangzhou-xihu
MainDemo遍历数据(休息5秒后),线程名称: main
key = 中国,value = china
以上,感谢。
2023年3月23日