结构分析
class Thread {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//环形数组,线性探测解决冲突
private Entry[] table;
}
}
常用方法
set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//获得当前线程的ThreadLocalMap
if (map != null)
map.set(this, value);
else
createMap(t, value);//还不存在就创建
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
初始化ThreadLocalMap的方法:做一些初始工作,并该键值对放入Entry[]中
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);
}
ThreadLocalMap的set方法
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();
//存在key,直接替换返回即可
if (k == key) {
e.value = value;
return;
}
//key已被回收
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 void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
//由于将数组看做环形,所以prevIndex
//从当前位置向前遍历,直到遇到空值,slotToExpunge为遍历过程中最前面的应该替换的旧值
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
//往后遍历
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 由于线性探测是向后的,可能找到key存在的元素
if (k == key) {
//交换当前的元素和在set()中找到的第一个旧元素的位置
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 向前没有找到旧元素,在这个方法中找到的第一个旧位置即为当前位置
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
//当前位置为旧元素,并且在这个方法中之前还没有遇到过旧元素,就将旧元素的首位置记为当前位置
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// If there are any other stale entries in run, expunge them
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 这是旧元素的位置,应该=null help GC
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
//Rehash until we encounter null
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//old value help GC
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
//探测
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;//返回遇到的空值的位置
}
//遍历过的i之前的旧元素都清除掉了,从i往后检查logn个位置
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 void rehash() {
//清除数组中所有旧元素,因为在之前的遍历中,都是遍历非空的一段,可能隔了一个空元素后面还有旧元素
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();//常规的resize
}
简单描述:
- 在探测过程中没有无效的元素,并找到了key对应的位置,直接更新即可
- 在探测过程中存在无效的元素,最终结果是将新元素放置在这个无效元素的位置,并尽可能做清理工作
- 在探测过程中没有无效的元素且没有找到key,就在空位置放置新元素,并做一次启发式清理和考虑扩容
get
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//获取当前线程的map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();//map还未创建
}
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;
}
ThreadLocalMap的getEntry()方法
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//直接定位到了即没有hash冲突,直接返回
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();
//找到key返回
if (k == key)
return e;
//清除旧元素
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
简单描述:
- 如果直接定位到了key,直接返回
- 存在冲突就探测,在探测过程中遇到了无效的元素就做清除,遇到了key就返回
- 没有找到key,返回null
remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap的remove方法
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();//清除引用,key会为null,再清除
expungeStaleEntry(i);
return;
}
}
}
abstract class Reference<T> {
public void clear() {
this.referent = null;
}
}
内存泄露
一些不会再被使用的对象,却没有被GC,就会造成内存泄露
产生原因:长生命周期的对象持有短生命周期的引用
内存泄露的例子:
public static void main(String[] args) {
List<Integer> list=new ArrayList<>();
Integer o =1;
list.add(o);
o=null;
System.out.println(list);
}
堆中的o由于存在list对它的引用,所以不会被GC回收,但是却不需要再使用它了,会造成内存泄露
解决内存泄露的原则:减小作用域和手动设置null
手动设置null参考ArrayList的remove()方法
public E remove(int index) {
//....
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
ThreadLocal内存泄露原因
ThreadLocal自身并不储存值,而是作为一个key来让线程从ThreadLocal获取value
Entry是中的key是弱引用,所以jvm在垃圾回收时如果外部没有强引用来引用它,ThreadLocal必然会被回收
但是,作为ThreadLocalMap的key,ThreadLocal被回收后,ThreadLocalMap就会存在key为null,但value不为null的Entry
- 若当前线程一直不结束,可能是作为线程池中的一员,线程结束后不被销毁
- 分配(当前线程又创建了ThreadLocal对象)使用了又不再调用get/set方法,就可能引发内存泄漏
- 就算线程结束了,操作系统在回收线程或进程的时候不是一定杀死线程或进程的,在繁忙的时候,只会清除线程或进程数据的操作,重复使用线程或进程(线程id可能不变导致内存泄漏)
因此,key弱引用并不是导致内存泄漏的原因,而是因为ThreadLocalMap的生命周期与当前线程一样长,并且没有手动删除对应key。
那么,为什么要将Entry中的key设为弱引用?相反,设置为弱引用的key能预防大多数内存泄漏的情况
- 如果key 使用强引用,引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏
- 如果key为弱引用,引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除
避免内存泄露的方法:在使用完ThreadLocal时,及时调用它的的remove方法清除数据。
InheritableThreadLocal
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
@Override
protected T childValue(T parentValue) {
return parentValue;
}
@Override
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
@Override
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
对这样一段程序
public static void main(String[] args) {
ThreadLocal<Integer> threadLocal=new ThreadLocal<>();
threadLocal.set(1);
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//null
threadLocal.set(2);
System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//2
}).start();
System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//1
}
若是用的InheritableThreadLocal
public static void main(String[] args) {
ThreadLocal<Integer> threadLocal=new InheritableThreadLocal<>();
threadLocal.set(1);
new Thread(()->{
System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//1
threadLocal.set(2);
System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//2
}).start();
System.out.println(Thread.currentThread().getName()+" "+threadLocal.get());//1
}
可以看到Thread 0得到了main线程中设置的值
Thread类中的init方法
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//.....
Thread parent = currentThread();
//....
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//...
}
可以看到子线程在创建的时候已经获得了父线程的inheritableThreadLocals
ThreadLocal的createInheritedMap方法
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
//复制ThreadLocalMap中所有不为空且key!=null的元素
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}