什么是ThreadLocal
ThreadLocal(本地线程变量)就是当前线程的变量,变量只属于当前线程,其他线程无权访问,从而实现了变量在线程间的隔离,保证了变量的线程安全。
举例应用
//创建线程
public class MyRunnable implements Runnable{
private ThreadLocal<?> threadLocal;
public MyRunnable(ThreadLocal<?> threadLocal){
this.threadLocal=threadLocal;
}
@Override
public void run() {
if(threadLocal.get()==null){
System.out.println("获取主线程变量失败");
}else{
System.out.println("获取成功");
}
}
//主线程
public class Test1 {
public static void main(String[] args) throws IOException {
ThreadLocal<String> local = new ThreadLocal<>();
String str="主线程变量";
local.set(str);
System.out.println(local.get());
Thread t = new Thread(new MyRunnable(local));
t.start();
}
结果
源码分析
set方法
public void set(T value) {
Thread t = Thread.currentThread();
//map的值就是键就是当前线程
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
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();
}
使用场景
代替参数的显式传递
controller到service将参数加入到ThreadLocal,传递ThreadLocal实现传递参数,这种场景使用比较少,一般都是显示传递参数,或者将参数封装成对象传递。
全局存储用户信息
当用户登录后,会将用户信息存入Token中返回前端,当用户调用需要授权的接口时,需要在header中携带 Token,然后拦截器中解析Token,获取用户信息,调用自定义的类(AuthNHolder)存入ThreadLocal中,当请求结束的时候,将ThreadLocal存储数据清空, 中间的过程无需在关注如何获取用户信息,只需要使用工具类的get方法即可。
public class AuthNHolder {
private static final ThreadLocal<Map<String,String>> loginThreadLocal = new ThreadLocal<Map<String,String>>();
public static void map(Map<String,String> map){
loginThreadLocal.set(map);
}
public static String userId(){
return get("userId");
}
public static String get(String key){
Map<String,String> map = getMap();
return map.get(key);
}
//防止内存泄漏
public static void clear(){
loginThreadLocal.remove();
}
}
解决线程安全问题
我们都知道@Autowired注解默认使用单例模式,那么不同请求线程进来之后,由于Dao层使用单例,那么负责数据库连接的Connection也只有一个,果每个请求线程都去连接数据库,那么就会造成线程不安全的问题,Spring为了使Connection达到线程间隔离,使用ThreadLocal,当请求获取数据库连接时,先从ThreadLocal获取Connection,如果为null,说明没有进行数据库连接,连接数据库后将Connection保存进ThreadLocal;
事务操作
存储事务线程信息。例如每次请求的id,以及请求的开始时间。隔离事务线程信息。
慎用的场景
线程池中由于采用线程复用原则,从而会破坏ThreadLocal达到的隔离效果。
异步程序中不等待返结果,直接向下执行,当出现返回结果时处理的就是另外一个线程。
每次使用完ThreadLocal都要调用remove() 方法,防止出现内存溢出,因为中使用的key为ThreadLocal的弱引用, 如果ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,但是如果value是强引用,不会被清理, 这样一来就会出现 key 为 null 的 value。