优雅的使用 ThreadLocal

前言

在我们日常 Web 开发中难免遇到需要把一个参数层层的传递到最内层,然后中间层根本不需要使用这个参数,或者是仅仅在特定的工具类中使用,这样我们完全没有必要在每一个方法里面都传递这样一个 通用的参数。如果有一个办法能够在任何一个类里面想用的时候直接拿来使用就太好了。JavaWeb项目大部分都是基于 Tomcat,每次访问都是一个新的线程,这样让我们联想到了 ThreadLocal,每一个线程都独享一个 ThreadLocal,在接收请求的时候 set特定内容,在需要的时候 get这个值。下面我们就进入主题。

ThreadLocal

维持线程封闭性的一种更规范的方法就是使用 ThreadLocal,这个类能使线程中的某个值与保存的值的对象关联起来。ThreadLocal提供 getset等接口或方法,这些方法为每一个使用这个变量的线程都存有一份独立的副本,因此 get总是返回由当前线程在调用 set时设置的最新值。ThreadLocal有如下方法

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

get()方法是用来获取 ThreadLocal在当前线程中保存的变量副本 set()用来设置当前线程中变量的副本 remove()用来移除当前线程中变量的副本 initialValue()是一个 protected方法,一般是用来在使用时进行重写的,如果在没有set的时候就调用 get,会调用 initialValue方法初始化内容。为了使用的更放心,我们简单的看一下具体的实现:

set方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

set方法会获取当前的线程,通过当前线程获取 ThreadLocalMap对象。然后把需要存储的值放到这个 map里面。如果没有就调用 createMap创建对象。

getMap方法

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

getMap方法直接返回当前 ThreadthreadLocals变量,这样说明了之所以说 ThreadLocal线程局部变量就是因为它只是通过 ThreadLocal变量存在了 Thread本身而已。

createMap方法

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

set的时候如果不存在 threadLocals,直接创建对象。由上看出,放入 mapkey是当前的 ThreadLocalvalue是需要存放的内容,所以我们设置属性的时候需要注意存放和获取的是一个 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)
                return (T)e.value;
        }
        return setInitialValue();
    }

get方法就比较简单,获取当前线程,尝试获取当前线程里面的 threadLocals,如果没有获取到就调用 setInitialValue方法, setInitialValue基本和 set是一样的,就不累累述了。

场景

本文应用 ThreadLocal的场景:在调用API接口的时候传递了一些公共参数,这些公共参数携带了一些设备信息,服务端接口根据不同的信息组装不同的格式数据返回给客户端。假定服务器端需要通过设备类型(device)来下发下载地址,当然接口也有同样的其他逻辑,我们只要在返回数据的时候判断好是什么类型的客户端就好了。如下:

场景一

请求

GET api/users?device=android

返回

{
        user : {        
        },
        link : "https://play.google.com/store/apps/details?id=***"
    }

场景二

请求

GET api/users?device=ios

返回

{
        user : {    
        },
        link : "https://itunes.apple.com/us/app/**"
    }

实现

首先准备一个 BaseSigntureRequest类用来存放公共参数

public class BaseSignatureRequest {
    private String device;

    public String getDevice() {
        return device;
    }

    public void setDevice(String device) {
        this.device = device;
    }
}

然后准备一个 staticThreadLocal类用来存放 ThreadLocal,以便存储和获取时候的 ThreadLocal一致。

public class ThreadLocalCache {
    public static ThreadLocal<BaseSignatureRequest>
        baseSignatureRequestThreadLocal = new ThreadLocal<>();
}

然后编写一个 Interceptor,在请求的时候获取 device参数,存入当前线程的 ThreadLocal中。这里需要注意的是,重写了 afterCompletion方法,当请求结束的时候把 ThreadLocal remove,移除不必须要键值对。

public class ParameterInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                             Object handler) throws Exception {
        String device = request.getParameter("device");
        BaseSignatureRequest baseSignatureRequest = new BaseSignatureRequest();
        baseSignatureRequest.setDevice(device);
        ThreadLocalCache.baseSignatureRequestThreadLocal.set(baseSignatureRequest);
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
                                Object handler, Exception ex) throws Exception {
        ThreadLocalCache.baseSignatureRequestThreadLocal.remove();
    }

    @Override
    public void postHandle(HttpServletRequest httpServletRequest,
                           HttpServletResponse httpServletResponse,
                           Object o, ModelAndView modelAndView) throws Exception {

    }
}

当然需要在 spring里面配置 interceptor

<mvc:interceptors>
        <mvc:interceptor>
            <mvc:mapping path="/api/**"/>
            <bean class="life.majiang.ParameterInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>

最后在 Converter里面转换实体的时候直接使用即可,这样就大功告成了。

public class UserConverter {
    public static ResultDO toDO(User user) {
        ResultDO resultDO = new ResultDO();
        resultDO.setUser(user);
        BaseSignatureRequest baseSignatureRequest = ThreadLocalCache.baseSignatureRequestThreadLocal.get();
        String device = baseSignatureRequest.getDevice();
        if (StringUtils.equals(device, "ios")) {
            resultDO.setLink("https://itunes.apple.com/us/app/**");
        } else {
            resultDO.setLink("https://play.google.com/store/apps/details?id=***");
        }
        return resultDO;
    }

总结

这种机制很方便,因为他避免了在调用每一个方法时都要传递执行上下文信息,合理的使用 ThreadLocal可以起到事倍功半的效果,但是需要避免滥用,例如将所有的全局变量作为 ThreadLocal对象, ThreadLocal类似全局变量,他能降低代码的可重用性,并在类之间引入隐含的耦合性,所以再使用前需要格外小心。

本文分享自微信公众号 - Java技术江湖(alicoder)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-07-02

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Java集合详解7:一文搞清楚HashSet,TreeSet与LinkedHashSet的异同

    《Java集合详解系列》是我在完成夯实Java基础篇的系列博客后准备开始写的新系列。

    Java技术江湖
  • Java集合详解7:一文搞清楚HashSet,TreeSet与LinkedHashSet的异同

    本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看

    Java技术江湖
  • Fork join并发框架与工作窃取算法剖析

    Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

    Java技术江湖
  • 优雅的使用 ThreadLocal

    在我们日常 Web 开发中难免遇到需要把一个参数层层的传递到最内层,然后中间层根本不需要使用这个参数,或者是仅仅在特定的工具类中使用,这样我们完全没有必要在每一...

    JAVA葵花宝典
  • java设计模式 (1) 工厂模式,抽象工厂模式,单子模式

    工厂模式就是实例化对象,用工厂方法代替new操作的一种模式,会给你系统带来更大的可扩展性和尽量少的修改量。

    曼路
  • 基于Springboot+Dubbo+Nacos 注解方式实现微服务调用

    今天跟大家分享基于Springboot+Dubbo+Nacos 注解方式实现微服务调用的知识。

    程序员小强
  • Java多线程,对锁机制的进一步分析

    可重入锁,也叫递归锁。它有两层含义,第一,当一个线程在外层函数得到可重入锁后,能直接递归地调用该函数,第二,同一线程在外层函数获得可重入锁后,内层函数可...

    用户1153489
  • APK安装流程详解1——有关"安装ing"的实体类概述

    该类包含了从AndroidManifest.xml文件中收集的所有信息。 PackageInfo.java源码地址 通过源码我们知道PackageInfo是...

    隔壁老李头
  • 驾驭Java线程池:定制与扩展

    Executor框架可以帮助将任务的提交和任务的执行解耦合,用户只需要将任务提交给Executor之后,其自会按照既定的执行策略来执行任务。但是要注意并不是所有...

    lyb-geek
  • 设计模式-创建型模式-工厂模式(工厂三兄弟)

    mySoul

扫码关注云+社区

领取腾讯云代金券