在java的多线程模块中,threadlocal是经常被提问到的一个知识点,提问的方式有很多种,可能是循序渐进也可能是就像我的题目那样,因此只有理解透彻了,不管怎么问,都能游刃有余。
这篇文章主要从以下几个角度来分析理解,以下源码基于jdk1.8。
1、threadlocal是什么
2、threadlocal怎么用
3、threadlocal源码分析
4、threadlocal内存泄漏问题
下面我们带着这些问题,一点一点揭开threadlocal的面纱。若有不正之处请多多谅解,并欢迎批评指正。
从名字我们就可以看到threadlocal叫做线程变量,意思是threadlocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。threadlocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
从字面意思来看非常容易理解,但是从实际使用的角度来看,就没那么容易了,作为一个面试常问的点,使用场景那也是相当的丰富:
1、在进行对象跨层传递的时候,使用threadlocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,session会话管理。
现在相信你已经对threadlocal有一个大致的认识了,下面我们看看如何用?
既然threadlocal的作用是每一个线程创建一个副本,我们使用一个例子来验证一下:
从结果我们可以看到,每一个线程都有各自的local值,我们设置了一个休眠时间,就是为了另外一个线程也能够及时的读取当前的local值。
这就是theadlocal的基本使用,是不是非常的简单。那么为什么会在数据库连接的时候使用的比较多呢?
上面是一个数据库连接的管理类,我们使用数据库的时候首先就是建立数据库连接,然后用完了之后关闭就好了,这样做有一个很严重的问题,如果有1个客户端频繁的使用数据库,那么就需要建立多次链接和关闭,我们的服务器可能会吃不消,怎么办呢?如果有一万个客户端,那么服务器压力更大。
这时候最好threadlocal,因为threadlocal在每个线程中对连接会创建一个副本,且在线程内部任何地方都可以使用,线程之间互不影响,这样一来就不存在线程安全问题,也不会严重影响程序执行性能。是不是很好用。
以上主要是讲解了一个基本的案例,然后还分析了为什么在数据库连接的时候会使用threadlocal。下面我们从源码的角度来分析一下,threadlocal的工作原理。
在最开始的例子中,只给出了两个方法也就是get和set方法,其实还有几个需要我们注意。
方法这么多,我们主要来看set,然后就能认识到整体的threadlocal了:
1、set方法
从set方法我们可以看到,首先获取到了当前线程t,然后调用getmap获取threadlocalmap,如果map存在,则将当前线程对象t作为key,要存储的对象作为value存到map里面去。如果该map不存在,则初始化一个。
ok,到这一步了,相信你会有几个疑惑了,threadlocalmap是什么,getmap方法又是如何实现的。带着这些问题,继续往下看。先来看threadlocalmap。
我们可以看到threadlocalmap其实就是threadlocal的一个静态内部类,里面定义了一个entry来保存数据,而且还是继承的弱引用。在entry内部使用threadlocal作为key,使用我们设置的value作为value。
还有一个getmap
调用当期线程t,返回当前线程t中的成员变量threadlocals。而threadlocals其实就是threadlocalmap。
2、get方法
通过上面threadlocal的介绍相信你对这个方法能够很好的理解了,首先获取当前线程,然后调用getmap方法获取一个threadlocalmap,如果map不为null,那就使用当前线程作为threadlocalmap的entry的键,然后值就作为相应的的值,如果没有那就设置一个初始值。
如何设置一个初始值呢?
原理很简单
3、remove方法
从我们的map移除即可。
ok,其实内部源码很简单,现在我们总结一波
(1)每个thread维护着一个threadlocalmap的引用
(2)threadlocalmap是threadlocal的内部类,用entry来进行存储
(3)threadlocal创建的副本是存储在自己的threadlocals中的,也就是自己的threadlocalmap。
(4)threadlocalmap的键值为threadlocal对象,而且可以有多个threadlocal变量,因此保存在map中
(5)在进行get之前,必须先set,否则会报空指针异常,当然也可以初始化一个,但是必须重写initialvalue()方法。
(6)threadlocal本身并不存储值,它只是作为一个key来让线程从threadlocalmap获取value。
ok,现在从源码的角度上不知道你能理解不,对于threadlocal来说关键就是内部的threadlocalmap。
只要是介绍threadlocal的文章都会帮大家认识一个点,那就是内存泄漏问题。我们先来看下面这张图。

上面这张图详细的揭示了threadlocal和thread以及threadlocalmap三者的关系。
1、thread中有一个map,就是threadlocalmap
2、threadlocalmap的key是threadlocal,值是我们自己设定的。
3、threadlocal是一个弱引用,当为null时,会被当成垃圾回收
4、重点来了,突然我们threadlocal是null了,也就是要被垃圾回收器回收了,但是此时我们的threadlocalmap生命周期和thread的一样,它不会回收,这时候就出现了一个现象。那就是threadlocalmap的key没了,但是value还在,这就造成了内存泄漏。