当前位置:网站首页>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;
}
}
}
}
边栏推荐
- #yyds dry goods inventory# Interview must brush TOP101: the last k nodes in the linked list
- DAO development tutorial [WEB3.0]
- Win11如何删除升级包?Win11删除升级包的方法
- 金鱼哥RHCA回忆录:CL210管理OPENSTACK网络--章节实验
- Map传值
- 生命周期和作用域
- WinRAR | 将多个安装程序生成一个安装程序
- 用VS2013编译带boost库程序时提示 fatal error C1001: 编译器中发生内部错误
- kubernetes - deploy nfs storage class
- Try compiling QT test on Allwinner V853 development board
猜你喜欢

MySQL中超键、主键及候选键的区别是什么

Redis启动时提示Creating Server TCP listening socket *:6379: bind: No error

Prometheus's Recording rules practice

PanGu-Coder:函数级的代码生成模型

【pyqt5】自定义控件 实现能够保持长宽比地缩放子控件

#yyds dry goods inventory# Interview must brush TOP101: the last k nodes in the linked list

Library website construction source code sharing

What should I do if the Win11 campus network cannot be connected?Win11 can't connect to campus network solution

深入浅出Flask PIN

【木棉花】#夏日挑战赛# 鸿蒙小游戏项目——数独Sudoku(3)
随机推荐
483-82(23、239、450、113)
[Neural Network] This article will take you to easily analyze the neural network (with an example of spoofing your girlfriend)
Prometheus's Recording rules practice
请你说说多线程
In the background of the GBase 8c database, what command is used to perform the master-slave switchover operation for the gtm and dn nodes?
安徽建筑大学&杭州电子科技大学|基于机器学习方法的建筑可再生能源优化控制
1065 A+B and C (64bit)
DAO development tutorial [WEB3.0]
Summer vacation second week wrap-up blog
[Server data recovery] Data recovery case of offline multiple disks in mdisk group of server Raid5 array
ExcelPatternTool: Excel form-database mutual import tool
深入浅出Flask PIN
【周赛复盘】LeetCode第304场单周赛
LeetCode 0151.颠倒字符串中的单词
When installing the GBase 8c database, the error message "Resource: gbase8c already in use" is displayed. How to deal with this?
短视频软件开发,Android开发,使用Kotlin实现WebView
explain 各字段介绍
驱动上下游高效协同,跨境B2B电商平台如何释放LED产业供应链核心价值?
Map传值
硬件大熊原创合集(2022/07更新)