JDK早期设计:每个ThreadLocal都创建一个Map, 然后用Thread(线程) 作为Map的key, 要存储的局部变量作为Map的value, 这样就能达到各个线程的局部变量隔离的效果。
JDK8 优化设计:每个Thread维护一个ThreadLocalMap, 这个Map的key是ThreadLocal实例本身,value才是真正要存储的值Object。ThreadLocal内部维护的是一个类似Map的ThreadLocalMap数据结构,key为当前对象的Thread对象,值为泛型的Object。使用ThreadLocal比较方便的就是当thread不变的情况下,可以很方便的设置或者获取对象。
static class ThreadLocalMap {
static class Entry extends WeakReference> {
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold; // Default to 0
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (Entry e : parentTable) {
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal
ThreadLocalMap是ThreadLocal的内部类,底层数据结构是一个数组,元素是Entry类,这个类是ThreadLocalMap的内部类,继承了WeakReference类
static class Entry extends WeakReference> { Object value; Entry(ThreadLocal> k, Object v) { super(k); value = v; } }
Entry中k是弱引用,也就是ThreadLocal,而value仍然是强引用,我们通常所说的内存泄漏原因也就在这个地方。
ThreadLocal使用场景-
ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全。
-
ThreadLocal 用作每个线程内需要独立保存信息,以便供其他方法更方便地获取该信息的场景。每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过 ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念。
public void set(T value) {
Thread t = Thread.currentThread();
//获取当前线程所保存的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
//如果为空先进行初始化
createMap(t, value);
}
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
当Entry的key为null,但是value是不为null的,这里造成了内存泄漏问题。
在Java中引用分为4种强软弱虚,强引用就是我们日常工作时用到的引用,比如User a = new User(),a就是强引用,软引用使用SoftReference包装,弱引用使用WeakReference包装,虚引用使用PhantomReference包装。
虚引用一般是用来链接堆外对象,通过虚引用实现对堆外内存的回收。弱引用每次发生GC的时候会被回收,而软引用只有在内存不足的时候才会被回收。
ThreadLocalMap中Entry的key就是通过弱引用修饰的,所以每次发生GC时会被回收掉,导致key变成null,而value是强引用,不会被回收,但是此时的value已经没有了任何意义,只是白白占着内存,所以也就导致了这部分内存不能被正常使用,造成内存泄漏。
ThreadLocal的getpublic T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//获取当前线程的ThreadLocalMap变量,如果为null,调用setInitialValue初始化
if (map != null) {
//如果ThreadLocalMap变量不为null,调用getEntry获取Entry元素
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
一直没找到则调用setInitialValue方法,将当前ThreadLocal实例作为key,null作为value,构造一个Entry插入到数组中
private Entry getEntry(ThreadLocal> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//如果计算出来的i索引位满足就返回,否则调用getEntryAfterMiss方法
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;
//e为null说明已经发生GC被回收掉了,返回null。
//否则,从i开始往后遍历,满足条件就返回,
//为null就清理,一直到e==null或者找到符合条件的为止
while (e != null) {
ThreadLocal> k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
ThreadLocal的reove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
//计算出当前ThreadLocal实例所在的i索引位,判断此位置的key是否是自己
//是,就删除,然后调用expungeStaleEntry方法看看能不能清理掉一些元素,然后返回。
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;
}
}
}
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
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal> k = e.get();
if (k == null) {
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的内存泄漏
- 内存溢出: Memory overflow 没有足够的内存提供申请者使用.
- 内存泄漏: Memory Leak 程序中已经动态分配的堆内存由于某种原因, 程序未释放或者无法释放, 造成系统内部的浪费, 导致程序运行速度减缓甚至系统崩溃等严重结果. 内存泄漏的堆积终将导致内存溢出
内存泄漏的发生跟 ThreadLocalIMap 中的 key 是否使用弱引用是没有关系的。在内存泄漏的情况中.都有两个前提:
1 、没有手动侧除这个 Entry:只要在使用完下 ThreadLocal ,调用其 remove 方法翻除对应的 Entry ,就能避免内存泄漏。
2 、 CurrentThread 依然运行:由于ThreodLocalMap 是 Threod 的一个属性,被当前线程所引用所以它的生命周期跟 Thread 一样长。那么在使用完 ThreadLocal 的使用,如果当前Thread 也随之执行结束, ThreadLocalMap 自然也会被 gc 回收,从根源上避免了内存泄漏。如果ThreadLocal对象设null了,开始发生“内存泄露”,然后使用线程池,这个线程结束,线程放回线程池中不销毁,这个线程一直不被使用,或者分配使用了又不再调用get,set方法,那么这个期间就会发生真正的内存泄露。
避免ThreadLocal的内存泄漏的最好办法是不用的ThreadLocal及时remove()掉



