当前位置:网站首页>ThreadLocal讲义
ThreadLocal讲义
2022-08-01 19:01:00 【罗罗的1024】
ThreadLocal
线程局部变量,属于线程自己本身的变量,对于其他线程是隔离,不可见的
线程变量存储在哪里数据结构里面呢?进入thread类,
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
//线程存储变量的容器map,在使用到局部变量时才会初始化
ThreadLocal.ThreadLocalMap threadLocals = null;
//.........省略部分代码..........
}
问题一:什么时候线程的这个map才会初始化呢?
怀揣着这个疑问,开始使用threadlocal
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
threadLocal.set(7);
我们发现 new ThreadLocal<>()
没有初始化线程的map,进入set方法看看
//设置线程变量
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的Map容器
ThreadLocalMap map = getMap(t);
if (map != null)
//this为threadlocal实例
map.set(this, value);
else
//创建map
createMap(t, value);
}
//初始化线程容器map
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
发现在线程第一次set 变量时,才会创建一个map来存储
问题二:线程变量存储在哪里?
进入new ThreadLocalMap(this, firstValue)
,看看变量具体是存储在 ThreadLocalMap 中的哪个数据结构
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//默认容量16
table = new Entry[INITIAL_CAPACITY];
//通过threadlocal实例的hash值计算出在entry数组中存放的位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//把数据放入到entry中
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
我们发现数据是存放在数据结构 entry
中,进入entry,看看这个数据结构是怎样的
//ThreadLocal 被弱引用包裹,当发生GC时,threadlocal将自动被清除
static class Entry extends WeakReference<ThreadLocal<?>> {
//线程变量存放在这个value中
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
public abstract class Reference<T> {
//引用key被存放到这里
private T referent;
//.....省略部分代码..........
}
发现线程变量存放在 entry 这个数据结构的 value 属性中,而这个key(也就是threadlocal实例),却被弱引用包裹,存放在Reference
的 referent 属性中, 当发生GC时,threadlocal 实例将自动被清除
问题三:很多文章都说threadlocal会造成内存泄漏呢,那究竟是怎么一个泄漏法呢
按照上面的流程,我们知道了,线程变量的值是存储在 entry 的 value 中,而threadlocal实例被WeakReference 装饰,也就是当发生GC时,threadlocal 实例将自动被清除,如果这个 threadlocal 实例被GC回收了,可是entry 中的 value 属性值 却和 真实的内存对象存在 强引用
关系,也就是说,这个没用的entry对象是无法被GC回收的
问题四:很多文章都说threadlcoal的get、set 方法会自动清除 key=null 的 entry 对象,那岂不是不会产生内存泄漏了????
伴随着上面的疑问,我们看看threadlocal的 get、set 方法
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//通过threadlocal实例的hash值计算出在entry数组中存放的位置
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
//该位置已经存放着一个threadlocal实例
ThreadLocal<?> k = e.get();
//是同一个threadlocal实例,直接覆盖之前的线程变量,结束
//所以一个threadlocal实例只能存放一个线程变量,要存放多个线程实例,就需要多个threadlocal实例
if (k == key) {
e.value = value;
return;
}
//如果之前的threadlocal实例由于GC变为null,先把之前entry的value置空,再在当前位置存放一个新创建的entry对象,结束
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//这个位置的entry数组是空的,直接把线程变量变成存储在entry中
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
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();
}
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//是当前的threadlocal实例,直接返回
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
//是当前的threadlocal实例,直接返回
if (k == key)
return e;
//当key 被回收时,
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
//把当中这个key为null的entry的vlaue置空
tab[staleSlot].value = null;
//清空当前位置数组中的引用
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
//把数组中所有的key=null的entry的value清空,然后再清空数组中的引用
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
//把当中这个key为null的entry的vlaue置空
e.value = null;
//清空当前位置数组中的引用
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
发现如果一直都是同一个threadlocal在 get、set ,是不会清除key为null的entry的,也就是说 ,是存在内存泄漏的可能的
所以在使用完threadlocal之后,最好手动调用 remove
方法来清除当前threadlocal实例以及线程变量值
//移除掉当前threadlocal实例以及当前实例对应的线程变量值
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
//通过threadlocal实例的hash值计算出在entry数组中存放的位置
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//调用WeakReference的clear方法清除对当前ThreadLocal实例的弱引用,也就是设置entry的key为null
e.clear();
expungeStaleEntry(i);
return;
}
}
}
ThreadLocal源码分析
public class ThreadLocal<T> {
//设置线程变量
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的Map容器
ThreadLocalMap map = getMap(t);
if (map != null)
//this为threadlocal实例
map.set(this, value);
else
createMap(t, value);
}
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();
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
//初始化线程容器
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//获取线程的map容器
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//threadlocal的静态内部类ThreadLocalMap
static class ThreadLocalMap {
private Entry[] table;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//默认容量16
table = new Entry[INITIAL_CAPACITY];
//通过threadlocal实例的hash值计算出在entry数组中存放的位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//把数据放入到entry中
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//通过threadlocal实例的hash值计算出在entry数组中存放的位置
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
//该位置已经存放着一个threadlocal实例
ThreadLocal<?> k = e.get();
//是同一个threadlocal实例,直接覆盖之前的线程变量,结束
//所以一个threadlocal实例只能存放一个线程变量,要存放多个线程实例,就需要多个threadlocal实例
if (k == key) {
e.value = value;
return;
}
//如果之前的threadlocal实例由于GC变为null,直接替换掉之前的实例,结束
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//这个位置的entry数组是空的,直接把线程变量变成存储在entry中
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
//移除掉当前threadlocal实例以及当前实例对应的线程变量值
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
//通过threadlocal实例的hash值计算出在entry数组中存放的位置
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//调用WeakReference的clear方法清除对当前ThreadLocal实例的弱引用,也就是设置entry的key为null
e.clear();
expungeStaleEntry(i);
return;
}
}
}
//删除旧的entry
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 将value显式地设置成null,去除entry中value的强引用,帮助GC
tab[staleSlot].value = null;
//把当前所在位置的entry数组设置为null
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
//对key为null的entry进行处理,将value设置为null,清除value的强引用
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
//ThreadLocal 被弱引用包裹,当发生GC时,threadlocal将自动被清楚
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
}
边栏推荐
- Redis启动时提示Creating Server TCP listening socket *:6379: bind: No error
- Zabbix6.0钉钉机器人告警
- 力扣刷题之求两数之和
- The life cycle and scope
- 小白系统初始化配置资源失败怎么办
- Industry Salon Phase II丨How to enable chemical companies to reduce costs and increase efficiency through supply chain digital business collaboration?
- 【木棉花】#夏日挑战赛# 鸿蒙小游戏项目——数独Sudoku(3)
- [Server data recovery] Data recovery case of offline multiple disks in mdisk group of server Raid5 array
- How to record and analyze your alchemy process - use notes of the visual artifact Wandb [1]
- Become a Contributor in 30 minutes | How to participate in OpenHarmony's open source contributions in multiple ways?
猜你喜欢
【综述专栏】IJCAI 2022 | 图结构学习最新综述:研究进展与未来展望
[pyqt5] Custom controls to achieve scaling sub-controls that maintain the aspect ratio
Prometheus的Recording rules实践
力扣刷题之移动零
在表格数据上,为什么基于树的模型仍然优于深度学习?
Website construction process
Hardware Bear Original Collection (Updated 2022/07)
MySQL database - stored procedures and functions
#yyds干货盘点# 面试必刷TOP101: 链表中倒数最后k个结点
在全志V853开发板试编译QT测试
随机推荐
金鱼哥RHCA回忆录:CL210管理OPENSTACK网络--章节实验
ClassID的计算中,&表示啥意思
用VS2013编译带boost库程序时提示 fatal error C1001: 编译器中发生内部错误
mysql函数的作用有哪些
Try compiling QT test on Allwinner V853 development board
对于web性能优化我有话说!
Library website construction source code sharing
力扣刷题之求两数之和
No need to crack, install Visual Studio 2013 Community Edition on the official website
腾讯云主机安全 x 轻量应用服务器|强强联合主机安全普惠版重磅发布
面试必问的HashCode技术内幕
Use of message template placeholders
【pyqt5】自定义控件 实现能够保持长宽比地缩放子控件
从普通进阶成优秀的测试/开发程序员,一路过关斩将
WinRAR | Generate multiple installers into one installer
在GBase 8c数据库后台,使用什么样的命令来对gtm、dn节点进行主备切换的操作?
使用常见问题解答软件的好处有哪些?
LeetCode 1374.生成每种字符都是奇数个的字符串
TestNG multiple xml for automated testing
Database Plus 的云上之旅:SphereEx 正式开源 ShardingSphere on Cloud 解决方案