前言:关于ThreadLocal,JDK文档中解释得有些晦涩,这几天看一个培训视频正好用到了该类,很多的弹幕兄弟也没整明白这是个啥,所以写篇文章谈下我对这个类的理解。
先上官方文档的中文解释:
这个类提供线程局部变量。 这些变量与其正常的对应方式不同,因为访问一个的每个线程(通过其get或set方法)都有自己独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。
个人解读:
首先,这个类的主要作用,是为每个线程提供一个专属于该线程的内存空间,这个内存空间是加密的,只有该线程才能访问该内存空间中的数据。所以这个类存在的目的,就是为了解决多线程存在的数据安全问题。
关于这个类的特点,总结如下:
- ThreadLocal 可以为当前线程关联一个数据。(它可以像 Map 一样存取数据,key 为当前线程)
- 每一个 ThreadLocal 对象,只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个 ThreadLocal 对象实例。
- 每个 ThreadLocal 对象实例定义的时候,一般都是 static 类型。
- ThreadLocal 中保存数据,在线程销毁后,会由 JVM 虚拟机自动释放。
现对该类的特点,做一个解读:
首先,做个类比,一个ThreadLocal实例化对象,就相当于是我们去超市的时候,门口的那一个“人脸识别式”的电子储物柜。
然后呢,每个去超市的人,都是一个线程。
接着,我们开始通过超市电子储物柜,理解这个ThreadLocal。
- 储物柜的特点一:
是共享的,只要来了这个超市的客人,就有资格将自己的个人物品放入这个储物柜,对吧?所以我们说,这个储物柜是static的,那么,类比过来,这个ThreadLocal在某个类中实例化时,也是static的。也就是上述ThreadLocal的特点3的解读。 - 储物柜的特点二:
由于是人脸识别,为每个来的顾客分配一个柜子放物品,所以我们说,一个人只有一张脸,因此一个人只能有一个柜子存储物品。而一个人就是一个线程,因此类比过来,一个ThreadLocal实例化对象,只能为每一个线程分配一个内存空间,这个内存空间存储该线程自己的专属数据。如果需要多个柜子存储自己的物品,那么这个顾客就需要去多个不同的电子储物柜中刷脸然后开箱放东西。所以如果一个线程需要存储多份数据,那么就需要有多个ThreadLocal实例化对象。而这,就是对上述ThreadLocal的特点2的解读。 - 储物柜的特点三:
在超市取电子柜中的物品的时候,顾客通过刷脸就可以打开自己的柜子,往里面放东西,或者取东西,这个过程,顾客是拿脸当钥匙,操作着属于自己的空间。而ThreadLocal实例化对象也是如此,为线程存储数据时,以该线程为key(相当于顾客的脸当钥匙),然后给该线程分配专属的内存空间,这里面可以给线程存放自己的数据,如变量、列表、数组、集合等,都可以存放。 只是比较特殊的是,生活中的电子储物柜,可以允许我们放一次东西进去,然后再接着放其他东西进去,而ThreadLocal比较不同,它比较霸道,它只允许你往里面放一份数据,而这份数据可以是普通变量,也可以是数组,或者列表,或者集合等,都可以,但最终这份数据的引用变量只能有一个,而这个引用变量就作为value存储进ThreadLocal中。 而这,就是对上述ThreadLocal的特点1的解读。 - 储物柜的特点四:
当天超市关门后,如果储物柜中的东西有客人(类比:线程)忘了取,那么超市(类比:虚拟机)会对柜子中的东西(类比:数据)进行清理,方便明天重新营业的时候其他顾客用这个柜子。而这个过程,客人已经走了,这个客人存储在柜子里的东西也被清理了。类比ThreadLocal中,当线程销毁后(类比:客人离开了超市),其存储在ThreadLocal实例化对象中的数据(类比:客人遗忘在超市储物柜中的东西),也会被虚拟机给清理掉(超市关门后清理柜子)。而这,就是对上述ThreadLocal的特点4的解读。
以上呢,就是我个人对ThreadLocal这个类的理解与解读,总的来说,一个ThreadLocal实例化对象,就相当于是一个超市的电子储物柜。一个超市可以有多个电子储物柜,所以我们的一个类中,也可以实例化多个ThreadLocal对象。 专业点来说,这个ThreadLocal对象,就是一个"线程域"。专门给每个线程存取自己的数据用的。
谈完了我个人对ThreadLocal类的作用的理解,再谈另一个问题,是关于ThreadLocal的底层实现,它是通过什么数据结构实现这种功能的呢?
其实呢,通过它的源码,可以看出它的底层其实是用了一个类似Map的结构。并且这个Map呢,与众不同的地方在于,它是一个线程安全的Map,可以想象成底层是Hashtable或ConcurrentHashMap。
那么,为什么要用线程安全的Map呢?它又有什么作用呢?
理由呢,很简单。首先,使用线程安全的Map,可以防止高并发的时候,多个线程同时存储数据时,可能占用了Map的同一个bucket位置(也就是Map底层数组的位置)。也就是说,当多个人同时准备去超市的储物柜存放东西的时候,人脸识别机器每次只能识别一张脸,打开一个柜子给顾客,如果这时候多个人拥上去同时刷脸,那么可能会导致打开一个柜子,却不知道是哪个顾客的脸刷出来的。所以我们就会发现,如果生活中多个人需要刷脸开柜子了,最好的处理方式就是大家排好队,一个一个刷脸,然后打开自己的专属柜子来存储物品,对吧?同样的道理,当多个线程同时需要往ThreadLocal中存储自己的数据时,使用线程安全的Map,那么同一时刻只允许一个线程操作该Map,也就是同一时刻只允许一个线程往该Map中存储数据,就不会导致数据混乱啦。
好了,以上就是我个人对JDK中的ThreadLocal这个类的解读了,如果有什么不恰当的地方,还望各位兄弟在评论区指出哦。
如果这篇文章对你有帮助的话,不妨点个关注吧~
期待下次我们共同讨论,一起进步哇~