优雅的使用 ThreadLocal
前言
在我们日常 Web
开发中难免遇到需要把一个参数层层的传递到最内层,然后中间层根本不需要使用这个参数,或者是仅仅在特定的工具类中使用,这样我们完全没有必要在每一个方法里面都传递这样一个 通用
的参数。如果有一个办法能够在任何一个类里面想用的时候直接拿来使用就太好了。 Java
的 Web
项目大部分都是基于 Tomcat
,每次访问都是一个新的线程,这样让我们联想到了 ThreadLocal
,每一个线程都独享一个 ThreadLocal
,在接收请求的时候 set
特定内容,在需要的时候 get
这个值。下面我们就进入主题。
ThreadLocal
维持线程封闭性的一种更规范的方法就是使用 ThreadLocal
,这个类能使线程中的某个值与保存的值的对象关联起来。 ThreadLocal
提供 get
和 set
等接口或方法,这些方法为每一个使用这个变量的线程都存有一份独立的副本,因此 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);elsecreateMap(t, value);}
set
方法会获取当前的线程,通过当前线程获取 ThreadLocalMap
对象。然后把需要存储的值放到这个 map
里面。如果没有就调用 createMap
创建对象。
getMap
方法
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
getMap
方法直接返回当前 Thread
的 threadLocals
变量,这样说明了之所以说 ThreadLocal
是 线程局部变量
就是因为它只是通过 ThreadLocal
把 变量
存在了 Thread
本身而已。
createMap
方法
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}
在 set
的时候如果不存在 threadLocals
,直接创建对象。由上看出,放入 map
的 key
是当前的 ThreadLocal
, value
是需要存放的内容,所以我们设置属性的时候需要注意存放和获取的是一个 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;}
}
然后准备一个 static
的 ThreadLocal
类用来存放 ThreadLocal
,以便存储和获取时候的 ThreadLocal
一致。
public class ThreadLocalCache {public static ThreadLocal<BaseSignatureRequest> baseSignatureRequestThreadLocal = new ThreadLocal<>();
}
然后编写一个 Interceptor
,在请求的时候获取 device
参数,存入当前线程的 ThreadLocal
中。这里需要注意的是,重写了 afterCompletion
方法,当请求结束的时候把 ThreadLocal
remove
,移除不必须要键值对。
public class ParameterInterceptor implements HandlerInterceptor {@Overridepublic 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;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {ThreadLocalCache.baseSignatureRequestThreadLocal.remove();}@Overridepublic 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
类似全局变量,他能降低代码的可重用性,并在类之间引入隐含的耦合性,所以再使用前需要格外小心。
最好的赞赏是关注
-结束-
近期文章推荐:
使用Kubespray部署Kubernetes集群
谈谈 API 网关
一个忙碌架构师的Java后端书架-2018
如何优雅的使用和理解线程池
-关注我-
优雅的使用 ThreadLocal相关推荐
- 京东一面:说说ThreadLocal的使用场景及使用方式
来源 | 网络 正文 两大使用场景-ThreadLocal的用途 典型场景1: 每个线程需要一个独享的对象(通常是工具类,典型需要使用的类有SimpleDateFormat和Random) 典型场景2 ...
- 京东一面:说出ThreadLocal的使用场景及使用方式
点击关注公众号,实用技术文章及时了解 两大使用场景-ThreadLocal的用途 典型场景1: 每个线程需要一个独享的对象(通常是工具类,典型需要使用的类有SimpleDateFormat和Rando ...
- 2018年Spring Cloud中国社区技术沙龙-成都站
Spring Cloud中国社区(http://springcloud.cn) 是国内基于Spring Cloud微服务体系创建的非盈利技术社区,发展至今刚好两周岁.自2016年10月份创建以来,在北 ...
- 详解JVM内存管理与垃圾回收机制2 - 何为垃圾
随着编程语言的发展,GC的功能不断增强,性能也不断提高,作为语言背后的无名英雄,GC离我们的工作似乎越来越远.作为Java程序员,对这一点也许会有更深的体会,我们不需要了解太多与GC相关的知识,就能很 ...
- Docker入门与实践
Docker简介 Docker是容器技术的一个代表,容器的技术从本质上讲是程序打包.隔离的技术,不是一个很新的技术,核心的技术在内核中已经存在很久了.但容器技术被大众所用,变成这么流行,这么火爆的技术 ...
- 限流降级神器-哨兵(sentinel)原理分析
Sentinel 是阿里中间件团队开源的,面向分布式服务架构的轻量级高可用流量控制组件,主要以流量为切入点,从流量控制.熔断降级.系统负载保护等多个维度来帮助用户保护服务的稳定性. 大家可能会问:Se ...
- Java并发与多线程
1.多线程优点 资源利用率更好:文件读写操作 程序设计在某些情况下更简单: 程序响应更快:端口监听操作 2.多线程的代价 设计更复杂:多线程共享数据时尤其需要注意 上下文切换的开销: CPU 会在一个 ...
- javaweb学习总结(二十五)——Apache的DBUtils框架学习
一.commons-dbutils简介 commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化 ...
- 怎样做才是最优雅方式切换 web 项目数据源 ?
随着业务变迁/需求变更,JavaEE 应用中会被迫连接多个数据源进行业务处理. 怎样在不影响原有项目结构的情况下,已最优雅/最简洁的方式动态切换数据源呢? 本文已一次添加数据源后动态切换实践为例,描述 ...
最新文章
- 微信小程序服务(功能)直达是什么?有什么作用?
- sqlserver2008r2数据库关联孤立账号的方法
- 替换ubuntu 源
- gnuradio android手机,如何搭配USRP在安卓设备上搭建GNU Radio
- GitHub标星14k:超详细的人工智能专家路线图
- php tp3.2 脚本大量数据操作思路
- CheckBoxList 只能选2个选项
- 打印从1到最大的n位数------2022/08/05
- 腾讯云图-一次性加载全部数据
- python 筛选 加字段_如何显示/隐藏显示在筛选中的字段,按选择分组?
- 云币网及KYC【区块链生存训练】
- SSM框架-SpringMVC详解,kafka常见的面试问题
- jQuery实现右下角滑动弹出可关闭重现层完整代码
- pytorch visdom安装启动问题
- 全力以赴提升粮食产能建设责任担当
- 如何像阿里工程师一样高效办公?
- 创业第三篇——我两次差点就成了狂飙中的石磊,创业中的贷款困境
- 大数据培训怎么选?16张国际公认的大数据“学历”证书
- 织梦Dedecms安全设置
- Windows应用程序无法正常启动(0xc0000013)的解决
热门文章
- BGA封装芯片手工焊接攻略
- mysql远程访问,修改root密码
- Oracle集合操作
- 【组队学习】【33期】动手学数据分析
- 【数据结构】判断一个单链表中各结点的值是否有序
- phpstudy2014 php7.0,phpstudy下载_phpStudy下载2014 官方版_php环境集成包 1.0_零度软件园...
- 2021腾讯数字生态大会:腾讯安全聚焦安全共建,护航数字经济发展
- 重磅!深度学习知识总结和调参技巧开放下载了
- 赠书 | 联邦学习如何在视觉领域应用?
- CSDN送会员?免费的!都来领!