线程局部变量,属于线程自己本身的变量,对于其他线程是隔离,不可见的
线程变量存储在哪里数据结构里面呢?进入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;
}
}
}
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;
}
}
}
}