持续更新IT互联网的技术干货,欢迎大家关注我。
第一问:你了解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。