优雅的使用 ThreadLocal

简介:

我们知道 JavaWeb项目大部分都是基于 Tomcat,每次访问都是一个新的线程,看到这里让我们联想到了 ThreadLocal,每一个线程都独享一个 ThreadLocal,在接收请求的时候 set特定内容,在需要的时候 get这个值。下面我们就进入主题。

ThreadLocal

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

 
  1. public T get() { }

  2. public void set(T value) { }

  3. public void remove() { }

  4. protected T initialValue() { }

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

set方法

 
  1. public void set(T value) {

  2. Thread t = Thread.currentThread();

  3. ThreadLocalMap map = getMap(t);

  4. if (map != null)

  5. map.set(this, value);

  6. else

  7. createMap(t, value);

  8. }

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

getMap方法

 
  1. ThreadLocalMap getMap(Thread t) {

  2. return t.threadLocals;

  3. }

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

createMap方法

 
  1. void createMap(Thread t, T firstValue) {

  2. t.threadLocals = new ThreadLocalMap(this, firstValue);

  3. }

set的时候如果不存在 threadLocals,直接创建对象。由上看出,放入 mapkey是当前的 ThreadLocalvalue是需要存放的内容,所以我们设置属性的时候需要注意存放和获取的是一个 ThreadLocal

get方法

 
  1. public T get() {

  2. Thread t = Thread.currentThread();

  3. ThreadLocalMap map = getMap(t);

  4. if (map != null) {

  5. ThreadLocalMap.Entry e = map.getEntry(this);

  6. if (e != null)

  7. return (T)e.value;

  8. }

  9. return setInitialValue();

  10. }

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

场景

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

场景一

请求

 
  1. GET api/users?device=android

返回

 
  1. {

  2. user : {

  3. },

  4. link : "https://play.google.com/store/apps/details?id=***"

  5. }

场景二

请求

 
  1. GET api/users?device=ios

返回

 
  1. {

  2. user : {

  3. },

  4. link : "https://itunes.apple.com/us/app/**"

  5. }

实现

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

 
  1. public class BaseSignatureRequest {

  2. private String device;

  3. public String getDevice() {

  4. return device;

  5. }

  6. public void setDevice(String device) {

  7. this.device = device;

  8. }

  9. }

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

 
  1. public class ThreadLocalCache {

  2. public static ThreadLocal<BaseSignatureRequest>

  3. baseSignatureRequestThreadLocal = new ThreadLocal<>();

  4. }

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

 
  1. public class ParameterInterceptor implements HandlerInterceptor {

  2. @Override

  3. public boolean preHandle(HttpServletRequest request, HttpServletResponse response,

  4. Object handler) throws Exception {

  5. String device = request.getParameter("device");

  6. BaseSignatureRequest baseSignatureRequest = new BaseSignatureRequest();

  7. baseSignatureRequest.setDevice(device);

  8. ThreadLocalCache.baseSignatureRequestThreadLocal.set(baseSignatureRequest);

  9. return true;

  10. }

  11. @Override

  12. public void afterCompletion(HttpServletRequest request, HttpServletResponse response,

  13. Object handler, Exception ex) throws Exception {

  14. ThreadLocalCache.baseSignatureRequestThreadLocal.remove();

  15. }

  16. @Override

  17. public void postHandle(HttpServletRequest httpServletRequest,

  18. HttpServletResponse httpServletResponse,

  19. Object o, ModelAndView modelAndView) throws Exception {

  20. }

  21. }

当然需要在 spring里面配置 interceptor

 
  1. <mvc:interceptors>

  2. <mvc:interceptor>

  3. <mvc:mapping path="/api/**"/>

  4. <bean class="life.majiang.ParameterInterceptor"></bean>

  5. </mvc:interceptor>

  6. </mvc:interceptors>

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

 
  1. public class UserConverter {

  2. public static ResultDO toDO(User user) {

  3. ResultDO resultDO = new ResultDO();

  4. resultDO.setUser(user);

  5. BaseSignatureRequest baseSignatureRequest = ThreadLocalCache.baseSignatureRequestThreadLocal.get();

  6. String device = baseSignatureRequest.getDevice();

  7. if (StringUtils.equals(device, "ios")) {

  8. resultDO.setLink("https://itunes.apple.com/us/app/**");

  9. } else {

  10. resultDO.setLink("https://play.google.com/store/apps/details?id=***");

  11. }

  12. return resultDO;

  13. }

总结

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


原文发布时间为:2018-09-15

本文作者:麻酱

本文来自云栖社区合作伙伴“Web项目聚集地”,了解相关信息可以关注“Web项目聚集地”。

相关文章
|
4月前
|
Java 测试技术 索引
ThreadLocal详解
文章详细讨论了Java中的`ThreadLocal`,包括它的基本使用、定义、内部数据结构`ThreadLocalMap`、主要方法(set、get、remove)的源码解析,以及内存泄漏问题和避免策略。`ThreadLocal`提供了线程局部变量,确保多线程环境下各线程变量的独立性,但不当使用可能导致内存泄漏,因此建议在不再需要`ThreadLocal`变量时调用其`remove`方法。
115 2
ThreadLocal详解
|
5月前
|
存储 Java
ThreadLocal应用及理解
ThreadLocal应用及理解
47 10
|
7月前
|
存储 Java 数据管理
ThreadLocal的使用
`ThreadLocal`是Java中的线程局部变量工具,确保每个线程都有自己的变量副本,互不干扰。适用于保持线程安全性数据和跨方法共享数据。基本用法包括创建实例、设置和获取值以及清除值。例如,创建ThreadLocal对象后,使用`.set()`设置值,`.get()`获取值,`.remove()`清除值。注意ThreadLocal可能引起内存泄漏,应适时清理,并谨慎使用以避免影响代码可读性和线程安全性。它是多线程编程中实现线程局部数据管理的有效手段。
89 10
|
存储 算法 安全
深入详解ThreadLocal
在我们日常的并发编程中,有一种神奇的机制在静悄悄地为我们解决着各种看似棘手的问题,它就是 ThreadLocal 。
21514 9
深入详解ThreadLocal
|
存储 安全 Java
ThreadLocal介绍和应用
ThreadLocal介绍和应用
66 0
|
缓存 安全 Java
浅谈ThreadLocal
浅谈ThreadLocal
149 0
|
存储 Java 数据库连接
对ThreadLocal的一点了解
ThreadLocal是线程变量,它为每个线程提供单独的存储空间。其主要作用是做线程间的数据隔离,也可以用于在同一个线程间方便地进行数据共享。(对于多线程资源共享,加锁机制采用“时间换空间”,ThreadLocal采用“空间换时间”)
130 0
|
存储 分布式计算 安全
什么是ThreadLocal?
这篇文章是慕课网上一门免费课程《ThreadLocal》的观后总结。这门课将ThreadLocal讲得非常清晰易懂,又深入底层原理和设计思想,是我看过的最好的ThreadLocal的资料,现在把用自己的话,把它整理成文字版本。 总共预计产出四篇文章,这是第一篇。
267 3
|
存储 Java
对threadlocal了解多少?
通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK 中提供的 ThreadLocal 类正是为了解决这样的问题。 ThreadLocal 类主要解决的就是让每个线程绑定自己的值,可以将 ThreadLocal 类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。