问题5:threadlocal

第一问:你了解ThreadLocal吗?

ThreadLocal主要用来存储当前线程上下文的变量信息,它可以保障存储进去的数据,只能被当前线程读取到,并且线程之间不会相互影响。

ThreadLocal提供了set和get函数,set函数表示把数据存储到线程的上下文中,get函数表示从线程的上下文中读取数据。通过get函数读取数据,类似于以当前线程线程为key从map中读取数据。

在实际的应用场景中,InheritableThreadLocal可能更常用,它不仅可以取出当前线程存储的数据,还可以在子线程中读取父线程存储的数据。某些业务场景中,需要开启子线程,InheritableThreadLocal就派上用场了。

第二问:ThreadLocal有哪些典型的应用场景?

1、数据库事务。

事务的实现原理非常简单,只需要在整个请求的处理过程中,用同一个connection开启事务、执行sql、提交事务就可以了。按照这个思路,实现起来也有两种方案,一种就是在第一次执行的时候 ,获取connection,在调用其他函数的时候,显示的传递connection对象。这种方案,只能存在于学习的demo中,无法应用到项目实践。

另一种方案就是通过AOP的方式,对执行数据库事务的函数进行拦截。函数开始前,获取connection开启事务并存储在ThreadLocal中,任何用到connection的地方,从ThreadLocal中获取,函数执行完毕后,提交事务释放connection。

2、web项目中的用户登录信息。

web项目中,用户的登录信息通常保存在session中。按照分层的设计理念,往往会被分成controller层、service层、dao层等等,还约定在service层是不能处理request、session等对象的。一种方案是调用service函数的时候,显示的传递用户信息;另一种方案则是用到了ThreadLocal,做一个拦截器,把用户信息放在ThreadLocal中,在任何用到用户信息的时候,只需要从TreadLocal中读取就可以了。

第三问:ThreadLocal实现原理是什么?

step1:首先看一下ThreadLocalMap,它是在ThreadLocal定义的一个内部类,看名字,就可以知道它用你来存储键值对的。只不过呢,它的Key只能是ThreadLocal对象。

step2:再来看一下Thread,它有个ThreadLocalMap类型的属性threadLocals。

step3:最后看一下get()函数的实现,得到当前线程的ThreadLocalMap,然后以当前的ThreadLocal对象为key,读取数据。这也就解释了为什么线程之间不会相互干扰,因为读取数据的时候,是从当前线程的ThreadLocalMap中读取的。

再来思考一下这个问题:

为什么get和set支持能存储一个对象,而不像HashMap那样通过Key存储多个对象呢?当使用ThreadLocal存储多个对象时,一种办法是多定义几个ThreadLocal对象,另一种办法是让ThreadLocal存储一个集合类,就会有这样的困扰,ThreadLocal为什么这样设计呢?

要回答这个问题,首先要明确一点,当做缓存的设计时,一定要考虑什么时候释放资源!这点对于基础类库尤其重要,可不希望由于程序员不好的编程习惯,导致内存溢出问题。如果允许ThreadLocal存储多个Key,也就是把清楚数据的工作负担推给了程序员,这显然是不负责任的。可能还有个解决方案,ThreadLocalMap设计成类似WeakHashMap的数据结构不就得了,很多缓存都是这么做的!一般的缓存,是允许cache miss的,发生了cache miss,只需要从源数据再次读取,而TreadLocal却不能这么设计。

来看一下Java类库的设计者们,是怎么解决这个问题的呢。看一下ThreadLocalMap中Entity的定义,它是一个WeakReference。这种定义的方式,就是说当ThreadLocal没有别的引用的时候,是允许被GC的。如果没有这样的定义,即使在程序中设置了变量为null,因为在ThreadLocalMap还有引用,ThreadLocal并不会被GC。

每个线程自身都维护着一个ThreadLocalMap,用来存储线程本地的数据,可以简单理解成ThreadLocalMap的key是ThreadLocal变量,value是线程本地的数据。就这样很简单的实现了线程本地数据存储和交互访问。

 

Entry继承自WeakReference,大家都知道WeakReference(弱引用)的特性,只要从根集出发的引用中没有有效引用指向该对象,则该对象就可以被回收,这里的有效引用并不包含WeakReference,所以弱引用不影响对象被GC。

这里被WeakReference引用的对象是哪个呢?可以看Entry的构造方法,很容易看出指的是ThreadLocal自身,也就是说ThreadLocal自身的回收不受ThreadLocalMap的这个弱引用的影响,让用户减轻GC的烦恼。

 

但是不用做些什么吗?这么简单?

其实不然,ThreadLocalMap还做了其他的工作,试想一下,ThreadLocal对象如果外界没有有效引用,是能够被GC,但是Entry呢?Entry也能自动被GC吗,当然不行,Entry还被ThreadLocalMap的table数组强引用着呢。

所以ThreadLocalMap该做点什么?

我看看ThreadLocalMap的expungeStaleEntry这个方法,这个方法在ThreadLocalMap get、set、remove、rehash等方法都会调用到,看下面标红的两处代码,第一处是将remove的entry赋空,第二次处是找到已经被GC的ThreadLocal,然后会清理掉table数组对entry的引用。这样entry在后续的GC中就会被回收。