ThreadLocal是通过线程隔离的方式防止任务在共享资源上产生冲突, 线程本地存储是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储。
ThreadLocal是一个将在多线程中为每一个线程创建单独的变量副本的类; 当使用ThreadLocal来维护变量时, ThreadLocal会为每个线程创建单独的变量副本, 避免因多线程操作共享变量而导致的数据不一致的情况。
注意:
但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。
笔者以汽车领域为案例,请求的处理会话线程,持有当前车机系统的信息:系统名称、版本,作为ThreadLocal的变量内容,实际的应用中通常将操作该变量的操作单独封装成一个类。
package com.linfanchen.springboot.lab.tr;
class OsBO {
/**
* 车机系统名称
*/
private String osName;
/**
* 车机系统版本
*/
private Integer version;
public String getOsName() {
return osName;
}
public void setOsName(String osName) {
this.osName = osName;
}
public Integer getVersion() {
return version;
}
public void setVersion(Integer version) {
this.version = version;
}
}
package com.linfanchen.springboot.lab.tr;
import java.util.HashMap;
public class LocalOS {
/**
* 线程持有的变量
*/
public static final ThreadLocal<HashMap<String, OsBO>> localOS = new ThreadLocal<>();
/**
* 展示车机系统
*/
static void showOs(String carName) {
// 输出本地变量
System.out.println(carName + " 的车机系统名称:" + localOS.get().get("os").getOsName() + ", 版本:" + localOS.get().get("os").getVersion());
// 清除本地变量的值,防止内存泄漏
localOS.remove();
}
}
package com.linfanchen.springboot.lab.tr;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.HashMap;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ThreadLocalTest {
@Test
public void firstTest() throws InterruptedException {
/**
* 创建线程,模拟实际场景中处理请求会话的线程
*/
new Thread(new Runnable() {
@Override
public void run() {
HashMap<String, OsBO> localOsValue = new HashMap<>();
OsBO osBO = new OsBO();
osBO.setOsName("IDrive");
osBO.setVersion(7);
localOsValue.put("os", osBO);
LocalOS.localOS.set(localOsValue);
// 获取当前线程的操作系统
LocalOS.showOs("BMW");
// 删除后再打印
System.out.println("BMW 线程下删除后再打印:" + LocalOS.localOS.get());
}
}, "BMW").start();
/**
* 创建线程,模拟实际场景中处理请求会话的线程
*/
new Thread(new Runnable() {
@Override
public void run() {
HashMap<String, OsBO> localOsValue = new HashMap<>();
OsBO osBO = new OsBO();
osBO.setOsName("MBUS");
osBO.setVersion(1);
localOsValue.put("os", osBO);
LocalOS.localOS.set(localOsValue);
// 获取当前线程的操作系统
LocalOS.showOs("Benz");
// 删除后再打印
System.out.println("Benz 线程下删除后再打印:" + LocalOS.localOS.get());
}
}, "Benz").start();
}
}
BMW 的车机系统名称:IDrive, 版本:7
Benz 的车机系统名称:MBUS, 版本:1
BMW 线程下删除后再打印:null
Benz 线程下删除后再打印:null
方法 | 作用 |
---|---|
ThreadLocal() | 创建ThreadLocal对象 |
public void set( T value) | 设置当前线程绑定的局部变量 |
public T get() | 获取当前线程绑定的局部变量 |
public T remove() | 移除当前线程绑定的局部变量,该方法可以帮助JVM进行GC |
protected T initialValue() | 返回该线程局部变量的初始值 |
这样设计的好处:
设置当前线程绑定的局部变量
public void set(T value) {
// 1.获取当前线程对象
Thread t = Thread.currentThread();
// 2.获取当前线程的threadLocals
ThreadLocal.ThreadLocalMap map = this.getMap(t);
if (map != null) { // 3.1如果 threadLocals 非空,添加 threadLocals 值,key为当前 ThreadLocal实例对象,value为传入的待设值
map.set(this, value);
} else { // 3.2如果 threadLocals 为空, 则进行创建初始化操作
this.createMap(t, value);
}
}
/**
* 根据当前线程获取线程内部的Thread.threadLocals属性(类型为: ThreadLocalMap)
*
* @param t the current thread 当前线程
* @return the map 对应维护的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* 创建初始化,key为当前 ThreadLocal 实例对象
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}
/**
* 获取当前线程中保存ThreadLocal的值
*
* 如果当前线程没有此ThreadLocal变量,则它会通过调用initialValue方法进行初始化值
*
*/
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocal.ThreadLocalMap map = this.getMap(t);
if (map != null) {
// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// 获取存储实体 e 对应的 value值,即为我们想要的当前线程对应此ThreadLocal的值
T result = e.value;
return result;
}
}
return this.setInitialValue();
}
/**
* 初始化值
*/
private T setInitialValue() {
T value = this.initialValue();
Thread t = Thread.currentThread();
// 根据当前线程获取map
ThreadLocal.ThreadLocalMap map = this.getMap(t);
if (map != null) {
// 获取到了map,则设置对应的值
map.set(this, value);
} else {
// 没有获取到则创建map
this.createMap(t, value);
}
// 判断对象的类是否 TerminatingThreadLocal,如果是则注册上去
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal)this);
}
return value;
}
/**
* 默认的初始值为null
* 如果想ThreadLocal线程局部变量有一个除null以外的初始值,
* 必须通过子类继承{@code ThreadLocal} 的方式去重写此方法
* 通常, 可以通过匿名内部类的方式实现
*/
protected T initialValue() {
return null;
}
执行流程:
/**
* 删除当前线程中保存的ThreadLocal对应的实体entry
*
*/
public void remove() {
// 1.获取当前线程对象中维护的ThreadLocalMap对象
ThreadLocal.ThreadLocalMap m = this.getMap(Thread.currentThread());
if (m != null) {
// 2.存在则调用map.remove
// 以当前ThreadLocal为key删除对应的实体entry
m.remove(this);
}
}
/**
* m.remove(this)为此方法
*/
private void remove(ThreadLocal<?> key) {
ThreadLocal.ThreadLocalMap.Entry[] tab = this.table;
int len = tab.length;
int i = key.threadLocalHashCode & len - 1;
// 遍历ThreadLocalMap
for(ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
// 比较每一个entry,找到相等的进行清理
if (e.get() == key) {
e.clear();
this.expungeStaleEntry(i);
return;
}
}
}
本质上来讲, 它就是一个Map, 但是这个ThreadLocalMap与我们平时见到的Map有点不一样:
参考文档:
https://blog.csdn.net/u010445301/article/details/111322569
https://blog.csdn.net/silence_yb/article/details/124265702
https://pdai.tech/md/java/thread/java-thread-x-threadlocal.html
https://zhuanlan.zhihu.com/p/34406557
https://www.cnblogs.com/eric-fang/p/13680015.html
https://blog.csdn.net/fascinate_/article/details/113524837