ThreadLocal的原理与使用场景

292 阅读3分钟

什么是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();
    }

结果 image.png

源码分析

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。