当前位置: 首页 > 科技观察

使用ThreadLocal优化代码

时间:2023-03-18 18:34:25 科技观察

最近接手一个老项目,看到一个很有意思的现象。本项目中大量的方法入参都会带来用户信息。例如,它的本意是在方法中使用用户信息,但给人的第一印象是,如此大规模地传输用户信息并不优雅。那么有什么办法可以优化吗?我们的第一反应是可以保存一个全局变量,将用户信息存放在初始位置的全局变量中,然后在需要的地方去获取。那么在WEB应用中,每个请求都是一个独立的线程,如何标记呢?可以使用线程id作为map的key,请求的用户信息作为map的value。没错,Java已经为我们封装了这样一个对象,就是我们今天要说的ThreadLocal。什么是ThreadLocal如何利用ThreadLocal优化userid逐层传输?ThreadLocal的原理是什么?ThreadLocal的实用要点1.ThreadLocal是什么?当前线程对应的变量通过get()和set()方法进行操作,不会与其他线程冲突,实现了基于线程的数据隔离。2.如何使用ThreadLocal进行优化话不多说,基于我们一开始的例子,我已经迫不及待的想使用ThreadLocal来进行优化了。2.1建立一个基于ThreadLocal的context定义一个SessionUser类来存储用户信息,包括用户id和用户名。然后定义一个基于ThreadLocal的上下文SessionUserContext,代码如下。2.2信息存储在ThreadLocal中在我们的优化案例中,存储的是用户信息。解析请求中的用户信息有多种方式。本文以HandlerIntercept为例来说明MVC中的一个方法。实现HandlerIntercept接口,重写preHandler方法,解析HttpServletRequest,获取用户信息用户信息存储在SessionUserContext源码中,如下所示。2.3在需要的地方获取信息所有需要传给CurrentUser的参数都可以去掉。当需要用户信息时,可以直接从SessionUserContext中获取。哈哈,是不是一下子清爽了许多呢?用户信息随处可取,无需层层传递用户信息。3.ThreadLocal实现原理上面我们已经知道了如何通过ThreadLocal进行优化。接下来,我们要知道它是什么以及为什么,我们来看看ThreadLocal的实现原理。3.1set方法Set方法应该是ThreadLocal的核心逻辑。主要三步:获取当前线程获取ThreadLocalMap对象如果ThreadLocalMap对象存在,则以当前线程对象为key,将要存入map的对象作为value如果ThreadLocalMap对象不存在,则调用creatMap()创建它3.2什么是ThreadLocalMap。ThreadLocalMap是在ThreadLocal类内部定义的静态类,它还定义了一个Entry类作为存储值的地方。ThreadLocalMap的key就是当前的ThreadLocal对象,value就是我们要存储的值(对象)。当调用creatMap时,会创建一个新的ThreadLocalMap对象。同时ThreadLocalMap作为一个属性存在于Thread类中。每个线程Thread维护一个Map,比如ThreadLocalMap。这个map的key就是LocalThread对象本身,value就是要存储的对象。3.3Get方法Get方法比较简单,就是从map中获取值的过程。3.4ThreadLocal小结下面我们重新梳理一下ThreadLocal是如何实现变量的线程隔离的:每个Thread都维护了一个ThreadLocalMap的引用。ThreadLocalMap是ThreadLocal的一个内部类,由Entry存储。value是传入的对象在调用ThreadLocal的get()/set()方法时,实际上是以ThreadLocal对象为key来读写ThreadLocalMap4中的value。实战要点在最开始的优化设计。注意对ThreadLocal的删除调用。这里需要说一下使用ThreadLocal时的两个关键点。特别是在使用线程池时使用ThreadLocal。4.1避免内存泄漏在引入ThreadLocalMap的时候,我们可以看出ThreadLocalMap是Thread的一个属性。所以ThreadLocalMap和Thread的生命周期是一样的。如果不手动删除对应的ThreadLocalkey,会造成无法恢复的内存泄露。特别是在线程池环境下,线程会不断被复用。4.2线程池避免了重复线程变量的影响。以上面的优化案例为例。在MVC中,每次有请求进来,都会使用线程池来复用线程。如果请求中携带了用户信息,则会重置ThreadLocal对应的用户信息。如果请求中没有携带用户信息,则必须手动清除当前ThreadLocal对应的变量,否则可能会在后面的使用中造成混乱。