ThreadLocal类详解
创始人
2024-04-02 21:19:19
0

ThreadLocal类注释翻译

打开JDK中ThreadLocal类源码,翻译类上注释如下(提取重点部分):

每个访问ThreadLocal实例对象的线程都有其自己的关于ThreadLocal对象的变量副本(通过get和set方法),只要线程存活而且ThreadLocal对象也存活,则线程都保留着与ThreadLocal对象的变量副本的隐式引用。线程停止后,其对应的threadlocal变量副本会被垃圾回收。

上面的翻译其实就表达了一个意思,即每个线程有自己的ThreadLocal对象,且Thread对象对ThreadLocal对象进行了隐式引用。如何进行的隐式引用呢?继续往下研究。

Entry解析

Entry是ThreadLocalMap类的一个内部类,其类注释如下:

该类对其key值ThreadLocal对象进行了弱引用,空键(即entry.get()==null)意味着该键不再被引用,因此可以从表中删除该项。在接下来的代码中,这些条目被称为“过时条目”。

看Entry实现:

 static class Entry extends WeakReference> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal k, Object v) {super(k);value = v;}}

Entry持有了ThreadLocal的弱引用。即Entry对象没有被GC回收,但是key值ThreadLocal对象可以被GC回收,此时这个Entry对象的key就是null,称为"过时条目"。

ThreadLocalMap解析

ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。ThreadLocal没有对外获取ThreadLocalMap对象的操作。ThreadLocalMap类是私有的,允许在Thread类中声明字段。为了处理非常大和长期的使用,hash entries 对key值使用弱引用。由于使用了弱引用,所以只有当entries空间满后,才会删除过时的entries。

上面是ThreadLocalMap类注释翻译。从上面翻译可以看出,ThreadLocal中对Thread本地存值,其实都是存到了ThreadLocalMap中。
在Thread类中,声明了ThreadLocalMap的变量,阅读Thread类源码,可知变量为:

/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;

由注释可知,这个成员变量由ThreadLocal类维护。即threadLocals变量的值,在ThreadLocal类中赋予。

下面看ThreadLocalMap中table属性:

 private Entry[] table;

table属性存储了本地线程中key-value键值对数组,因为一个线程可以存放多个key-value,所以是数组形式。上面提到,Entry的key是ThreadLocal对象,所以,在Thread本地变量中,key就是ThreadLocal对象,value就是我们要存的值,而这个key-value对象又存放在ThreadLocalMap中的Entry对象中。在Thread类中,有ThreadLocalMap对象的属性,这样就可以获取到任意存放的本地变量。这样这个功能的逻辑就形成了闭环。

但是jdk在设计这个功能时有些特殊,甚至说有些别扭。因为我们在给一个线程对象存取本地变量时,都是通过ThreadLocal对象的get()和set()方法来进行操作的。而我们上面又提到,ThreadLocal是线程对象本地变量Map中的一个key。用key来操作Map,把自己当成key存到Map中,这个操作是不是很另类呢?

ThreadLocal类方法

initialValue()方法

 protected T initialValue() {return null;}

初始化值。用于没有给ThreadLocal对象调用set()方法赋值,而直接调用get()方法取值时进行返回。即创建了ThreadLocal对象后,没有给其set()值,则调用get()时返回initialValue()方法的值。这个方法一般在创建ThreadLocal时通过内部类方式进行实现。

get()方法

public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}

先看第一行,获取到当前线程,即谁调用ThreadLocal对象的get()方法,就获取到谁的线程,
第二行调用getMap()方法,参数是当前线程,看getMap()方法源码:

ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

返回了当前线程的threadLocals属性,这个属性我们上面分析过,就是当前线程的ThreadLocalMap,而这个Map中存放了当前线程的本地变量。

然后,调用ThreadLocalMap的getEntry()方法,参数是this,即当前的ThreadLocal对象,以此为key,找对应value。

总体流程就是: ThreadLocal作为key,它的get()方法首先获取到当前线程,然后再获取到当前线程的本地变量Map,即ThreadLocalMap,然后在把ThreadLocal自己作为key,传入ThreadLocalMap的getEntry()方法中,获取对应的value。这个设计别扭吧。

set()方法

  public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}

跟get()方法逻辑类似,获取到当前线程,然后获取到当前线程的ThreadLocalMap成员属性,然后调用Map的set方法,key就是ThreadLocal对象本身,value是参数设置的值。set()进去的key和value最终加入到Entry[] table中去。
在这里插入图片描述

关于ThreadLocal内存泄漏问题

Thread类里引用了ThreadLocalMap对象,ThreadLocalMap中Entry对象对ThreadLocal对象进行了弱引用。所以自始至终,Thread对象和ThreadLocal对象都没有发生直接关系。所以,ThreadLocal对象在出栈后,会被GC。此时,Entry中key为null。但是value还是有值的。所以,这个Entry就在内存中,无法获取了。这就是内存溢出问题。示意图如下:
在这里插入图片描述

那为什么Entry要设置成对ThreadLocal的弱引用呢?为何不设置成强引用呢?因为如果设置成弱引用,有可能发生内存溢出,如果设置成强引用,Entry[]不被GC,ThreadLocal就不会GC。假如ThreadLocal没用了,也不会被回收,肯定造成了内存溢出。
采用弱引用后,ThreadLocal可以被回收了,就进行回收,虽然有造成内存溢出可能,但是不是一定会造成内存溢出。而且在ThreadLocal对象的set()和get()方法中,会检查当前Thread的ThreadLocalMap中是否有key为null的Entry清除掉。
想要避免ThreadLocal内存溢出问题,就在程序中及时调用remove()方法,及时删除掉不用的Entry对象,这样就不会有内存溢出问题了。

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...