环境:jdk8
类定义
public class ThreadLocal<T>
构造方法
public ThreadLocal() {
}
可以看到ThreadLocal类是一个范型定义的类,实例化时可以指定实际类型。
该方法实例化一个线程本地变量。
initialValue
initialValue方法源码:
protected T initialValue() {
return null;
}
可以看到该方法是protected修饰的,是一个范型方法,默认返回null。可以被子类重写。
该方法返回当前线程局部变量的当前线程的“初始值”。
该方法第一次被调用是一个线程通过get方法访问这个变量,除非这个线程预先调用set方法,在这种情况下,该方法将不会被当前线程调用。
正常情况下,该方法最多会被每个线程都调用一次。除非该线程调用remove方法后又调用get方法。
如果需要线程本地变量有一个初始值,那么该方法必须被重写,通常在匿名内部类中重写。
set
set方法源码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
首先第二行得到当前运行线程的引用。接下来再调用getMap方法返回当前线程关联的ThreadLocalMap对象:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap类是一个自定义的hash map仅用来维持一个threadlocal类。该类作为静态内部类定义在ThreadLocal类中。
在getMap方法中返回当前线程的threadLocals变量,我们去看看Thread类中该变量的定义:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
根据该变量的注释,我们知道,threadLocals由ThreadLocal类维持,而ThreadLocal的值和当前线程相关。
接下来set方法的第四行判断与当前线程关联的ThreadLocalMap对象是否为空,若不为空,则调用map的set方法以ThreadLocal为key,value为值:
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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();
}
该方法的第8~10行初始化,第12~26根据key去遍历Entry实体数组是否存在传递过来的ThreadLocal,存在则返回,否则调用replaceStaleEntry方法替换旧的Entry实体并返回,当计算得到的Entry为空时,第28行实例化一个Entry,建立关联。
否则调用createMap方法使用当前ThreadLocal对象和当前要设置的值value实例化一个ThreadLocalMap对象,建立map和当前ThreadLocal的关联。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue); // 这里是上面提到的threadLocals初始化的地方
}
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);
}
INITIAL_CAPACITY常量的值为16.
可以看到会构造一个Entry数组,用来存放一个ThreadLocal和它的值。
注意这里使用到了延迟初始化的技术:
这里仅仅是初始化了16个元素的引用数组,并没有初始化16个 Entry 对象。而是一个线程中有多少个线程局部对象要保存,那么就初始化多少个 Entry 对象来保存它们。
到了这里,我们可以思考一下,为什么要这样实现了。为什么要用 ThreadLocalMap 来保存线程局部对象呢?原因是一个线程拥有的的局部对象可能有很多,这样实现的话,那么不管你一个线程拥有多少个局部变量,都是使用同一个 ThreadLocalMap 来保存的,ThreadLocalMap 中 private Entry[] table 的初始大小是16。超过容量的2/3时,会扩容。
第四行Entry构造方法存在于静态嵌套类Entry中:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
在该方法中会构造一个弱引用的ThreadLocal对象。
第五行size=1,即table元素数量为1。只有在我们至少有一个ThreadLocal放入table的时候才会创造一个ThreadLocalMap实例。
最后将关联对象ThreadLocalMap赋值给当前线程的thread-local变量。
由此可以看到set方法设置当前线程的thread-local变量的副本并指定一个初始值,每个线程都保持对其线程局部(thread-local)变量副本的隐式引用。
get
get方法源码:
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();
}
首先第二行得到当前运行线程的引用。接下来再调用getMap方法返回当前线程关联的ThreadLocal map对象:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap类是一个自定义的hash map仅用来维持一个threadlocal类。
接下来判断与当前线程关联的map对象是否为空,不为空,则通过map的getEntry方法得到与ThreadLocal关联的Entry,
接下判断Entry不为空则,得到Entry的值并返回,该值是和ThreadLocal对象关联的值。
否则返回调用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;
}
可以看到该方法是set()方法的变种,为了确定并返回初始值。
remove
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();
expungeStaleEntry(i);
return;
}
}
}
table变量的定义:
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
由以上分析可以知道table存储的是弱引用的ThreadLocal对象。
第五行根据key的哈希码得到的索引去遍历查找table里面的的Entry并得到Entry持有的引用判断其是否和传递进来的ThreadLocal对象相等,若相等则清除它,并调用expungeStaleEntry方法将索引为i的Entry置为null,数量减1,最后重新计算哈希码。
内存回收
在ThreadLocal 层面的内存回收
根据ThreadLocal类的注释,可以知道每个活着的线程都持有一个thread-local变量的副本并且ThreadLocal实例是可访问的。
当线程死亡时,那么所有的保存在的线程局部变量就会被回收,其实这里是指线程Thread对象中的 ThreadLocal.ThreadLocalMap threadLocals 会被回收,这是显然的。
/**Each thread holds an implicit reference to its copy of a thread-local
variable as long as the thread is alive and the {@code ThreadLocal}
instance is accessible; after a thread goes away, all of its copies of
thread-local instances are subject to garbage collection (unless other
references to these copies exist). */
ThreadLocalMap 层面的内存回收
根据ThreadLocalMap类的注释,ThreadLocalMap是一个定制的hash map,只用来维持线程局部变量并且是被WeakReferences包装过的弱引用。
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
如果线程可以活很长的时间,并且该线程保存的线程局部变量有很多(也就是 Entry 对象很多),那么就涉及到在线程的生命期内如何回收 ThreadLocalMap 的内存了,不然的话,Entry对象越多,那么ThreadLocalMap 就会越来越大,占用的内存就会越来越多,所以对于已经不需要了的线程局部变量,就应该清理掉其对应的Entry对象。
/**
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
使用的方式是,Entry对象的key是WeakReference 的包装,当ThreadLocalMap 的 private Entry[] table,已经被占用达到了三分之二时 threshold = 2/3(也就是线程拥有的局部变量超过了10个) ,就会尝试回收 Entry 对象,我们可以看到 ThreadLocalMap.set方法中有下面的代码:
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
cleanSomeSlots 就是进行回收内存:
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;
}
e.get() == null 调用的是 Entry 的父类 WeakReference<ThreadLocal<?>> 的方法:
public T get() {
return this.referent;
}
返回 null ,表示 Entry 的 key 已经被回收了,所以可以回收该 Entry 对象了:
expungeStaleEntry(i)方法源码:
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;
}
总结
1)一个线程中的所有的局部变量其实存储在该线程自己的同一个map属性中;
2)线程死亡时,线程局部变量会自动回收内存;
3)线程局部变量时通过一个 Entry 保存在map中,该Entry 的key是一个 WeakReference包装的ThreadLocal, value为线程局部变量;
key 到 value 的映射是通过:ThreadLocal.threadLocalHashCode & (INITIAL_CAPACITY - 1) 来完成的;
4)当线程拥有的局部变量超过了容量的2/3(没有扩大容量时是10个),会涉及到ThreadLocalMap中Entry的回收.
如有疏漏或不清楚请指出,谢谢!
内存回收及总结内容来自: