宇智波程序笔记47-用ThreadLocal来优化下代码吧 1.什么是ThreadLocal 2.如何使用ThreadLocal进行优化 2.1 构建基于ThreadLocal的上下文 2.2 信息存入ThreadLocal中 2.3 在需要的地方获取信息 3.ThreadLocal实现原理 3.1 set方法 3.2 ThreadLocalMap是什么。 3.3 get方法 3.4 ThreadLocal小结 4.实战要点 4.1 避免内存泄露 4.2 线程池避免重复线程变量影响

最近接手了一个老项目,看到一个很有意思的现象。

这个项目中大量的方法入参都会带上user信息,比如这样

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

它的意图是希望在方法内使用user的信息,但是如此大范围的传递用户信息,第一感觉就是不优雅。那有什么办法可以优化一下呢?

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

我们第一反应是,可以存一个全局变量,在初始位置将用户信息存入全局变量,然后在需要的地方去get一下。

那在WEB应用中,每个请求都是一个独立线程,怎么去标记呢?

可以用线程的id去作为map的key,将该请求的用户信息作为map的value。

咦?很熟悉的感觉。

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

没错,Java已经帮我们封装好了这么一个对象,它就是我们今天要说的ThreadLocal。

  • 什么是ThreadLocal

  • 如何使用ThreadLocal优化userid层层传递的问题

  • ThreadLocal原理是啥

  • ThreadLocal的实战要点

先来看下JDK的注释:

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

简单翻译过来,就是说:

ThreadLocal提供了线程隔离的局部变量,通过get( )和set( )方法操作当前线程对应的变量,而且不会和其他线程冲突,实现了基于线程的数据隔离。

2.如何使用ThreadLocal进行优化

话不多说,基于我们开头的例子,我迫不及待地用ThreadLocal来优化一下。

2.1 构建基于ThreadLocal的上下文

定义一个SessionUser类,存储用户信息,包括用户id、用户名。

然后定义一个基于ThreadLocal的上下文SessionUserContext,代码如下所示。

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响


2.2 信息存入ThreadLocal中

在我们的优化案例中,就是存入用户信息。

解析请求中的用户信息有很多方法。本文以HandlerIntercept为例,说明下MVC中的一种方式。

  • 实现HandlerIntercept接口

  • 重写preHandler方法

  • 解析HttpServletRequest,获取用户信息

  • 用户信息存于SessionUserContext

源码如下所示。

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

2.3 在需要的地方获取信息

原本需要传入CurrentUser的参数都可以去掉了。

在需要用户信息的时候,直接从SessionUserContext中获取即可。

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

哈哈,是不是看起来一下子清爽了很多。

可以在任何地方获取user信息,不再需要层层传递用户信息了。

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

3.ThreadLocal实现原理

上面我们已经知道了怎么通过ThreadLocal进行优化。

下面,我们要 知其然知其所以然,一起看看ThreadLocal实现原理吧。

3.1 set方法

Set方法应该是ThreadLocal的核心逻辑了。

主要三步:

  • 获取当前线程

  • 获取ThreadLocalMap对象

  • 如果ThreadLocalMap对象存在,则将当前线程对象作为key,要存储的对象作为value存到map中 如果ThreadLocalMap对象不存在,就调用creatMap( )进行创建

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

3.2 ThreadLocalMap是什么。

ThreadLocalMap是一个定义在ThreadLocal类内部的静态类,里面还定义了一个Entry类作为存储值的地方。

ThreadLocalMap的key是当前ThreadLocal对象,value是我们要存储的值(对象)。

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

调用creatMap的时候,就是新建一个ThreadLocalMap对象

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

同时,ThreadLocalMap在Thread类中作为一个属性存在。

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

每个线程Thread维护了ThreadLocalMap这么一个Map,这个map的key是LocalThread对象本身,value则是要存储的对象

3.3 get方法

Get方法就比较简单了,就是从map中取值的过程。

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

3.4 ThreadLocal小结

xmlns:xsi="http://www.lanboyulezc.cn /2001/XMLSchema-instance"
  
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://www.dongdongrji.cn maven.apache.org/xsd/maven-4.0.0.xsd">
  
  <artifactId>dubbo<www.xxinyoupt.cn /artifactId>
  
  <groupId>nacos</groupId>
  
  <version>0.0.1-SNAPSHOT<www.lanboyulezc.cn /version>
  
  <priority value=www.lanboylgw.com/"info"www.chuanchenpt.cn/ />
  
  <appender-ref ref=www.yixingxzc.cn"stdout"www.lanboylgw.com />
  
  <appender-ref ref="fileout"www.baichuangyule.cn /
  
  换掉encoder的实现类或者换掉layout的实现类就可以了
  
  <?xml version= www.lanboyulezc.cn www.jiuerylzc.cn"1.0"www.zhuyngyule.cn encoding=www.bhylzc.cn"UTF-8"?>
  
  <configuration debug=www.shicaiyulezc.cn"false"www.huachenguoj.com >
  
  <property name=www.51huayuzc.cn"APP_NAME" value="logtest"/>
  
  <property name=www.xinhuihpw.com "LOG_HOME" value="./logs" www.wanyaylezc.cn//>
  
  <appender name="STDOUT" class="www".yachengyl.cn"ch.qos.logback.core.ConsoleAppender">
  
  <!--替换成AspectLogbackEncoder-->
  
  <encoder class="www.shengrenyp.cn "com.yomahub.tlog.core.enhance.logback.AspectLogbackEncoder"www.51huayuzc.cn>
  
  <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{www.pinguo2yl.com} - %msg%n</pattern>
  
  <appender www.baishenjzc.cn name="FILE" www.baihua178.cn class="ch.qos.logback.core.rolling.RollingFileAppender">
  
  <File>${LOG_HOME}/${APP_www.jinliyld.cn NAME}.log<www.baihua178.cn /File>
  
  <rollingPolicy class="www.jintianxuesha.com"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"www.wanyaylezc.cn/>
  
  <FileNamePattern>www.yachengyl.cn ${LOG_HOME}/${APP_NAME}.log.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
  
  <MaxHistory>30<www.51huayuzc.cn /MaxHistory>
  
  <maxFileSize>1000MB<www.jinliyld.cn /maxFileSize>

现在,让我们重新梳理一遍,看看ThreadLocal是如何实现变量的线程隔离的:

  • 每个Thread维护着一个ThreadLocalMap的引用

  • ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储,key是ThreadLocal对象,值是传递进来的对象

  • 调用ThreadLocal的get()/set()方法时,实际上就是以ThreadLocal对象为key,在ThreadLocalMap中读写value

4.实战要点

在一开始的优化设计中,不知道大家有没有注意到对ThreadLocal的remove调用。

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

这里就需要谈谈ThreadLocal使用时的,两个要点。尤其是在使用线程池的时候使用ThreadLocal。

4.1 避免内存泄露

宇智波程序笔记47-用ThreadLocal来优化下代码吧
1.什么是ThreadLocal
2.如何使用ThreadLocal进行优化
2.1 构建基于ThreadLocal的上下文
2.2 信息存入ThreadLocal中
2.3 在需要的地方获取信息
3.ThreadLocal实现原理
3.1 set方法
3.2 ThreadLocalMap是什么。
3.3 get方法
3.4 ThreadLocal小结
4.实战要点
4.1 避免内存泄露
4.2 线程池避免重复线程变量影响

在ThreadLocalMap介绍的时候,我们可以看到,ThreadLocalMap是Thread的一个属性。因此,ThreadLocalMap和Thread的生命周期是一样的。

如果没有手动删除对应的ThreadLocal的key,那么就会造成内存泄漏无法回收。尤其在线程池环境下,线程会被不断复用。

4.2 线程池避免重复线程变量影响

以上文优化案例为例。

在MVC中,每次请求进来会使用线程池复用线程。如果请求带了用户信息,那么就会重置ThreadLocal对应的用户信息,如果请求没有带用户信息,必须手动清除一下当前ThreadLocal对应的变量,否则后面使用过程中可能会造成混乱。