天天看点

面试官:知道ThreadLocal嘛?谈谈你对它的理解?

在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嘛?谈谈你对它的理解?

上面这张图详细的揭示了threadlocal和thread以及threadlocalmap三者的关系。

1、thread中有一个map,就是threadlocalmap

2、threadlocalmap的key是threadlocal,值是我们自己设定的。

3、threadlocal是一个弱引用,当为null时,会被当成垃圾回收

4、重点来了,突然我们threadlocal是null了,也就是要被垃圾回收器回收了,但是此时我们的threadlocalmap生命周期和thread的一样,它不会回收,这时候就出现了一个现象。那就是threadlocalmap的key没了,但是value还在,这就造成了内存泄漏。