栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

ThreadLocal原理与使用

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

ThreadLocal原理与使用

ThreadLocal原理与使用

最近看到公司的系统管理组件中获取人员信息的一段代码:

        //获取登录用户信息
 UserLoginInfo userLoginInfo = ThreadLocalUtils.getUserLoginInfo();

getUserLoginInfo()方法本以为是通过token解析或者是通过token查redis。但是点进去却发现是从ThreadLocal中获取登陆信息?

   
    private static final ThreadLocal> local = ThreadLocal.withInitial(HashMap::new);

	public static UserLoginInfo getUserLoginInfo() {
        return get(Constants.USER_LOGIN_INFO);
    }
	
    public static  T get(String key) {
        return ((T) local.get().get(key));
    }

一番百度学习后发现Threadlocal是线程间做数据隔离的。这时我又想起之前学习的volatile保证数据变量的可见性。数据为什么又隔离又可见。区别是什么?不禁混乱,下面是个人的梳理

volatile

volatile是Java的一个关键字,一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰后,就具备了两层语义:保证了不同线程对这个变量进行操作时的可见性和禁止了指令重排序。

Java中变量是存放在主内存中,线程并不能直接读取和操作主内存的变量,而是会将变量读到工作内存中进行操作。而volatile的作用是保证所有线程能第一时间获取变量的最新值。但是要注意的是,对volatile修饰的变量的更改操作并不是原子性的。因为更改操作包括读变量,修改变量和写入工作内存。而volatile只是保证 了读变量是读的最新值。所以,volatile无法保证对变量的任何操作都是原子性的。

ThreadLocal

ThreadLocal会为每个使用该变量的线程提供独立的变量副本,保证各个线程之间的隔离。探究底层,ThreadLocal是通过静态内部类ThreadLocalMap实现的。

下面是ThreadLocal的类图结构,从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap

和我所想的不同的是,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。

set方法
public void set(T value) {
    //(1)获取当前线程(调用者线程)
    Thread t = Thread.currentThread();
    //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //(3)如果map不为null,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //(4)如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
}

在上面的代码中,(2)处调用getMap方法获得当前线程对应的threadLocals(参照上面的图示和文字说明),该方法代码如下

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}

如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals,该方法如下所示

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。

get方法

在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null

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

threadLocal 和threadLocalMap的关系如下图所示。

threadlocal内存泄漏问题

上图我们可以看到,threadLocal最终还是调用的threadlocalMap类的方法,而threadlocalMap的内部其实是一个Entry数组

static class Entry extends WeakReference> {
            
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

当前ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的key是ThreadLocal的弱引用。如果 key threadlocal 为 null 了,这个 entry就清除了。

当threadlocal为null或者被垃圾回收后,但ThreadLocalMap(threadlocals)是线程的内部属性,它的生命周期与所属线程同步。并不会被垃圾回收。所以此时ThreadLocalMap(threadlocals)就会出现一个现象:key为空,但是value值还在。这就是内存泄漏问题。

避免方法

在调用ThreadLocal最后一定要调用remove()方法。

接着文章开头的从threadLocal中获取用户登陆信息。发现项目中有一个拦截器,每次请求都会将token中获得的用户信息存入threadLocal中。这样后面的service层代码就可以直接通过threadlocal获取,而不需要通过参数传递。

@Slf4j
public class UserInterceptor extends HandlerInterceptorAdapter {
    @Autowired
    UserService userService;
    @Autowired
    MenuService menuService;
    @Resource
    private AuthorizationService authorizationService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       //获取token
        String token = request.getHeader(Constants.USER_TOKEN_HEADER);
        //通过token获取用户信息并放入ThreadLocal中
        if (StringUtil.isBlank(token) ) {
            UserloginInfo userloginInfo = userService.getLoginInfo(token)
            ThreadLocalUtils.setUserLoginInfo(userloginInfo);
        }
        //后续一系列校验等等
          .....
          .....
          .....
          .....
        return super.preHandle(request, response, handler);
    }
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //业务结束后清空threadlocal
        ThreadLocalUtils.clear();
        super.afterCompletion(request, response, handler, ex);
    }

最后附上项目的threadLocalUtils吧。核心还是对ThreadLocal的get、set的使用

public final class ThreadLocalUtils {

    private ThreadLocalUtils() {
    }

    private static final ThreadLocal> local = ThreadLocal.withInitial(HashMap::new);

    
    public static Map getAll() {
        return local.get();
    }

    
    public static  T put(String key, T value) {
        local.get().put(key, value);
        return value;
    }

    
    public static void remove(String key) {
        local.get().remove(key);
    }

    
    public static void clear() {
        local.remove();
    }

    
    public static  T get(String key) {
        return ((T) local.get().get(key));
    }

    
    public static  T get(String key, Supplier supplierOnNull) {
        return ((T) local.get().computeIfAbsent(key, k -> supplierOnNull.get()));
    }

    
    public static  T getAndRemove(String key) {
        try {
            return get(key);
        } finally {
            remove(key);
        }
    }


    public static void setUserLoginInfo(UserLoginInfo loginInfo) {
        put(Constants.USER_LOGIN_INFO, loginInfo);
    }

    public static UserLoginInfo getUserLoginInfo() {
        return get(Constants.USER_LOGIN_INFO);
    }


    public static void setAppLoginInfo(AppLoginInfo loginInfo) {
        put(Constants.APP_LOGIN_INFO, loginInfo);
    }

    public static AppLoginInfo getAppLoginInfo() {
        return get(Constants.APP_LOGIN_INFO);
    }


    public static void setAppName(String appName) {
        put(Constants.APP_NAME, appName);
    }

    public static String getAppName() {
        return get(Constants.APP_NAME);
    }

    public static void setSysLog(SysLog sysLog){
        put(Constants.OPERATE_LOG,sysLog);
    }

    public static SysLog getSysLog(){
        return get(Constants.OPERATE_LOG);
    }

}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1004049.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号