> 文档中心 > ThreadLocal入门,看完这个应该差不多了

ThreadLocal入门,看完这个应该差不多了

为什么对ThreadLocal感兴趣?

ThreadLocal从字面上来理解,就是线程本地变量。因为它只对线程本身有效,所以在今天这种大谈特谈多线程的并发编程的情况下,来讨论ThreadLocal,我觉得怎么也不过分。

ThreadLocal基本原理

先看下多线程下的JVM模型

 主内存中存储的变量为进程共享变量。

在堆(Heap)中的变量在被线程使用的时候会复制一个变量到副本线程的本地内存中,

当进程中的变量被改变之后,都会通过JMM(Java内存模型)回传写入到主内存中,

这样的情况下,任意线程修改主内存的共享变量都将会使其他线程中改变量值发生改变.

主内存中的共享变量关系如下所示:

 线程A和线程B都有属于自己的变量。线程之间的通信是有自己规定的方式的。

ThreadLocal就是线程自己的共享变量,ThreadLocal通过在本地线程内部设置对应的值,而不对外公布调用权限,以致于其能保证内容的唯一和外部不可以修改性。

ThreadLocal常用方法

常用的方法就三板斧:

其实就三个常见的方法:set get remove

set 赋值

直接将变量值复制到ThreadLocal中进行存储。

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

get 取值

    /     * 返回在当前线程副本中的线程局部变量.如果此变量不存在于当前线程中,      * 它优先通过调用initialValue方法来初始化值.     *     * @return the current thread's value of this thread-local     */    public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) {     ThreadLocalMap.Entry e = map.getEntry(this);     if (e != null) {  @SuppressWarnings("unchecked")  T result = (T)e.value;  return result;     } } return setInitialValue();    }

根据存储的key,取出对应的存储对象。和redis的key-value基本上操作一致。

remove清空

移除线程本地变量。

    /     * Removes the current thread's value for this thread-local     * variable.  If this thread-local variable is subsequently     * {@linkplain #get read} by the current thread, its value will be     * reinitialized by invoking its {@link #initialValue} method,     * unless its value is {@linkplain #set set} by the current thread     * in the interim.  This may result in multiple invocations of the     * {@code initialValue} method in the current thread.     *     * @since 1.5     */     public void remove() {  ThreadLocalMap m = getMap(Thread.currentThread());  if (m != null) {      m.remove(this);  }     }

ThreadLocal能解决什么问题?

资源锁问题

并发编程,永远也避不开的一个话题就是:锁,资源抢占的问题。

swap、乐观锁、悲观锁。你发现在面试的时候,面试官有各种各样的问题,但是无论什么情况下,涉及到锁这个问题,是如果也避不开的。

而ThreadLocal的本地局部特性,可以很好的绕开多线程下并发所产生的锁问题。

补充说明

锁和ThreadLocal都能解决多线程的资源共享问题,只是两者在实现的方式上存在的一定的差异。

锁:以时间换空间,实现的方式是只提供一份变量,让多个线程使用排队访问(临界区排队)。例如多个部门的人去找财务盖章报销的情况,因为盖章只能一个个盖,所有到了盖章的时候,需要一个个的来。 

ThreadLocal:以空间换时间,实现的方式是每个线程都提供一份变量副本,来实现同时访问的时候,互不干扰。例如还是盖章的情况,让每个部门配一个财务相关人员,每个部门部门内部盖章就可以了。

访问安全问题

你有一个思想,我有一个思想。咱们共享,这是个很好的事情。

但是你有一张银行卡,我有一张银行卡。咱们共享,这让大家都很难堪的事情。

而ThreaLocal因为线程内部访问的特性,导致其对于外部是不可见的,这样你的银行卡你自己用就好了,我的银行卡我自己保管就可以了。数据泄露的风险就完全规避掉了。

你可以尝试在多线程中去get返回值,你可以清楚的看到,不同的thread中,返回的值是不一样的,同样的thread中,返回的值是不变的。

数据透传问题

假如你有一个以下的问题:

如果你有些公共的参数,它对于每个人或者终端来讲都是不同的;你现在需要根据不同的终端,传递来的Hearder的参数头,来计算得到当前终端类型,并且根据终端类型来计算并且返回不同的数据。

例如H5需要返回H5的数据,APP需要返回APP的数据。

如果是你来设计的话,你会怎么处理这个问题?

假设这样来设计:在每个方法中声明,参数从Controller层传递到Service中,可能这个service还需要调用其他的service,传递来传递去,这样侵入方法太深,修改原来的代码多,这中间会增加大量的开发和测试时间,以及一些其他的未知风险。

这个时候你会怎么办?

有的人想到了使用redis来存储,但是redis中存储的话,需要根据不同的人来存储,假如用户量比较多的话,并且用户两多终端同时切换使用,就会导致后端计算错误,没有办法返回正确的数据。

这个时候用ThreadLocal解决就方便很多了。

因为ThreadLocal的线程唯一性,所以每次请求都是一个新的线程。

我们可以写个ThreadLocalUtil类,封装个Map 在Controller层请求的时候,使用拦截器,获取对应的hearder参数Map,然后把参数set到ThreadLocal的Map中。

这样在后台service中处理的时候,只是需要使用ThreadLocal中的get方法,将数据取出来就可以了。

中间的数据传递根本就不需要管了。

在拦截器中当方法执行完成后,再去调用下remove方法。这样下次再有请求进来的时候也不会有什么影响了。

公共参数传递

这个和上面差不多,就不废话了

总结

ThreadLocal 虽然好用,但是不是万能的,走异步的时候,它还是会有些坑的。

这个时候,你可以考虑下InheritableThreadLocal,但是这个也不是万能的哦,你可以自己试试看

开发者涨薪指南 ThreadLocal入门,看完这个应该差不多了 48位大咖的思考法则、工作方式、逻辑体系零基础学唱歌网