问题:在登录时把用户信息放在threadlocal中,但在并发调用过程中部分线程获取到的threadlocal值为空

解决:通过学习这篇博客,发现子线程获取不到主线程的threadlocal值,可改用InheritableThreadLocal

 

ThreadLocal问题

​ThreadLocal ​​是JDK提供的,它提供了线程本地变量。什么是线程本地变量呢?其实就是你创建了一个​​Threadlocal​​变量,每个访问​​Threadlocal​​变量的线程都有一个本地副本。我们看下面的图:

ThreadLocal并发问题总结_get方法从上面看出你创建一个​​ThreadLocal​​变量,每个访问该的线程都会复制到自己的本地,所以线程操作的都是本地的副本,这也就是说每个线程都是操作的自己本地的变量,那就完美的避免了线程安全的问题。

在这里还有一个问题。我在写这篇文章的时候看过很多文章,总的来说就是​​ThreadLocal​​就是为了解决多线程并发问题而提供的一种方法,还有一种解释就是​​ThreadLocal​​的最终目的就是为了解决多线程访问共享资源所产生的。真的对吗?​​ThreadLocal​​并没有共享那么从何而来的同步呢?

自己的想法

在看了​​Java并发编程之美​​后我所理解的​​Threadlocal​​提供了线程本地变量的副本,每个线程实际操作的时自己本地的变量副本,也就是说该变量副本只能当前线程访问,就不存在多个线程共享的问题,从​​Threadlocal​​名字我们也能看出​​本地线程​​。那那那它也就不存在去解决并发问题了。

如何使用

我们来看下面的例子。

ThreadLocal并发问题总结_set方法_02

输出结果:

Thread[Thread-1,5,main]====57

Thread[Thread-0,5,main]====75

创建了两个线程,它们都在​​threadlocal​​上面都set了一个随机数,我们看最后得输出结果每个都是不同得值,那么我们如果把​​threadlocal​​替换成一个集合会发生什么,由于两个线程时上个线程生成的随机数57会被第二个线程覆盖掉,而在​​Threadlocal​​中两个线程都是操作的自己的本地副本,那么两个线程互不影响都无法操控到对方的数据,因此它们存取的都是不同的值。

实现原理

那么​​Threadlocal​​是如何实现的呢?在研究​​Threadlocal​​的实现原理我们先看一下​​Thread​​的内部属性。

ThreadLocal并发问题总结_get方法_03

  • threadLocals 此线程保存的​​Threadlocal​​的值

inheritableThreadLocals等到后面再说。

在​​Thread​​的内部属性中我们看到了这两个默认为null的属性,threadLocals用来保存​​Threadlocal​​的本地副本,默认是为null只有调用​​Threadlocal​​的set时才会创建。也就是说​​Threadlocal​​就类似一个工具,它的作用就是把value的值通过set存在线程每个线程的threadLocals 中,只要线程一直存在threadLocals 也就一直存在。所以当不需要使用本地变量的时候可以调用​​Threadlocal​​的remove来清空本地变量。而threadLocals 为什么继承鱼ThreadLocalMap呢?ThreadLocalMap是一个定制的HashMap,而使用Map的原因就是可以每个线程关联多个​​Threadlocal​​变量。

set方法

我们来看一下set方法是如何实现的。

ThreadLocal并发问题总结_主线程_04

可以看出流程非常简单,首先获取当前线程然后在进行下一步操作,我们在看一下getMap做了什么

ThreadLocal并发问题总结_主线程_05

getMap主要就是返回了当前threadLocals的属性。那如果map为空呢?

ThreadLocal并发问题总结_子线程_06

如果map为空的话就直接创建一个新的ThreadLocalMap。

我们来看一下流程图。

ThreadLocal并发问题总结_主线程_07

get方法

看一下Get方法

ThreadLocal并发问题总结_set方法_08

首先根据当前线程获取实例如果存在就返回,如果不存在就先初始化一个空值,然后判断如果当前threadLoacals不为空就直接set一个空,否则就创建一个变量。

remove方法

ThreadLocal并发问题总结_实现原理_09

remove方法相对来说比较简单。

总结

​Threadlocal​​的实现原理其实就是通过set把value set到线程的threadlocals属性中,​​threadlocals​​类型是Map其中的Key就是​​Threadlocal​​的this引用,value就是我们所set的值,如果当前线程不销毁的话​​threadlocals​​会一直存在。一直存在的话可能会造成内存溢出,所以使用完之后尽量remove一下。不过在这里又有一个问题那就是如果我的线程想要读取主线程的变量要怎么做?我们上面的例子都是设置的新创建的线程,那么现在我在主线程中set一个值,这个时候我在新创建的线程中可以读取到吗?答案是不可以,因为​​Threadlocal​​不支持继承性。

我们看下面的例子:

ThreadLocal并发问题总结_get方法_10

输出结果:

Thread[Thread-0,5,main]====null

也就是说​​Threadlocal​​不支持继承性,主线程设置了值,在子线程中是获取不到的。那我现在想要获取主线程里面的值要怎么做?

​Threadlocal​​是实现不了的,不过​​Threadlocal​​有一个子类可以实现。​​InheritableThreadLocal​​,​​InheritableThreadLocal​​是​​Threadlocal​​的实现,我们来看一个简单的例子。

ThreadLocal并发问题总结_主线程_11

输出结果:

Thread[Thread-0,5,main]====1000

运行结果发现子线程是可以获取到主线程设置的值的,那它是如何实现的?

我们看一下代码实现:

ThreadLocal并发问题总结_get方法_12

​InheritableThreadLocal​​是继承​​Threadlocal​​的,并且把​​threadlocals​​给替换成​​inheritableThreadLocals​​了所以上面的​​inheritableThreadLocals​​我要留在最后说,那么替换成​​inheritableThreadLocals​​后子线程就可以获取到主线程设置的属性了吗?我们在看一下Thread的实现。

ThreadLocal并发问题总结_实现原理_13

看​​Thread​​的初始化方法可以看出,先获取了当前线程(主线程)判断主线程的​​inheritableThreadLocals​​不为空的话就调用createInheritedMap方法赋值给子线程中的​​inheritableThreadLocals​​。具体这里解释太多。有机会在写一篇文章来解释。

关于慎用ThreadLocal

使用ThreadLocal要注意remove !

ThreadLocal的实现是基于一个所谓的ThreadLocalMap , 在ThreadLocalMap中,它的key是一个弱引用。通常弱引用都会和引用队列配合清理机制使用,但是ThreadLocal是个例外,它并没有这么做。

这意味着,废弃项目的回收依赖于显式地触发,否则就要等待线程结束,进而回收相应ThreadLocalMap !这 就 是 很 多

OOM的来源,所以通常都会建议,应用一定要自己负责remove , 并 且 不 要 和 线 程 池 配合,因为worker 线 程 往 往 是 不 会 退 出 的 。