Java 中的 ThreadLocal
Java juc 面试 About 6,486 words说明
这里的内存泄露指的是:Thread
长时间运行,尤其是线程池中的线程,由于Thread
中的threadLocals
中的Entry
数组包含了关联ThreadLocal
的弱引用和ThreadLocal
设置的值的元素Entry
,在没有调用remove
的情况下,发生了垃圾回收,ThreadLocal
被回收,而ThreadLocal
设置的值还保存在Entry
中,整个Entry
也一直不为null
,线程一直持有该Entry
引用,得不到回收,占用内存,造成内存泄漏。
内存泄漏问题
set
后没有remove
。- 同一代码块未
set
方法和未重写initValue
方法,直接调用get
方法,没有进行remove
。 remove
后再get
。
set 后没有 remove 情况
当threadLocal
未被回收前,Thread
中的成员变量threadLocals
(就是ThreadLocalMap
)的Entry
数组中持有threadLocal
引用和threadLocal
设置的value
(设置在6
号索引位置的Entry
)。
GC
回收后,由于没有调用remove
方法,Entry
数组仍然持有threadLocal
设置的Entry
,虽然referent
被回收置空了,但value
仍在。
threadLocal2
设置一个value
时(设置在13
号位索引的Entry
),可以看到Entry
数组中有两个不为null
的元素(6
号和13
号)。
当threadLocal2
被remove
后,发现threadLocal2
设置的整个13
号Entry
对象都被置空回收了,只留了原先threadLocal
的6
号Entry
对象。
new Thread(() -> {
Thread thread = Thread.currentThread();
ThreadLocal<Object> threadLocal = new ThreadLocal<>();
try {
threadLocal.set(thread.getName());
} finally {
// 故意不 remove
}
threadLocal = null;
System.gc();
ThreadLocal<Object> threadLocal2 = new ThreadLocal<>();
try {
threadLocal2.set(thread.getName());
} finally {
threadLocal2.remove();
}
threadLocal2 = null;
System.gc();
},"AAA").start();
未 set 直接 get 后没有 remove 情况
在未调用set
方法及重写initValue
初始化方法时,get
方法会初始化一个value
为null
的Entry
并放入ThreadLocalMap
的Entry
数组,同样没有进行remove
操作。
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
fixedThreadPool.execute(() -> {
Thread thread = Thread.currentThread();
ThreadLocal<Object> threadLocal = new ThreadLocal<>();
try {
threadLocal.set(thread.getName());
} finally {
// threadLocal.remove();
}
threadLocal = null;
System.gc();
ThreadLocal<Object> threadLocal2 = new ThreadLocal<>();
Object o = threadLocal2.get();
threadLocal2 = null;
System.gc();
});
remove 后再次 get 情况
remove
后再次调用get
也会出现内存泄漏问题。
哈希碰撞问题
放入ThreadLocal
到Entry
数组时,如果发现已经索引位置上已经有其他的元素了,则判断,如果是同一个key
就替换value
,如果key
为null
(key
为null
的情况为先remove
后get
)就替换老的Entry
,都不是就将索引往后移一位,循环判断。
这次处理哈希碰撞是移动到数组下一位索引。HashMap
处理哈希碰撞是使用数组加链表的方式,哈希值冲突了就放到链表末尾(Java8
)。
set 回收未移除的 Entry
Java8
中ThreadLocal
的set
方法会尽可能的判断Entry
数组中的Entry
对象的key
是否为空,为空则移除。
源码分析
ThreadLocal
的set
方法实则是调用ThreadLocalMap
中的set
方法,然后调用cleanSomeSlots
判断Entry
数组中Entry
的key
是否null
,等于null
则调用expungeStaleEntry
清除该无用的Entry
(若Entry
数组大小大于等于threshold
就进行rehash
)。
cleanSomeSlots
方法中判断的次数是每次循环后n
无符号右移1
位,若找到一个threadLocal
为null
的,再将n
的值赋值为len
长度,继续循环直到n
为0
,因为n
的值是2
的x
幂,故没有threadLocal
为空的情况下只会循环x
次就跳出循环了。
所以set
方法,只是尽可能的去弥补没有remove
带来的问题,但还是有很大可能删除不到。
主要源码
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
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 T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
i = nextIndex(i, len);
Entry e = tab[i];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
// ...
return i;
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
}
————        END        ————
Give me a Star, Thanks:)
https://github.com/fendoudebb/LiteNote扫描下方二维码关注公众号和小程序↓↓↓