ThreadLocal
常用方法
set/get/remove三个方法,这些方法都是操作Thread对象的ThreadLocalMap对象去实现的
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);//没有获取到ThreadLocalMap, 新建一个
}
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();//没有获取到ThreadLocalMap ,新建一个并赋值为初始化的值
}
remove
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this); //this表示ThreadLocal对象
}
数据结构
每个线程对象Thread中都有一个ThreadLocalMap
ThreadLocalMap是ThreadLocal的内部静态类
Entry是继承自WeakReference<ThreadLocal<?>>,通过查看构造器中的super(k),发现Entry对象中的k是弱引用
可以发现,每个Thread对象中持有ThreadLocalMap对象,ThreadLocalMap是通过数组实现的,数组中的元素是Entry对象,Entry对象中key是ThreadLocal对象的弱引用,value是存储的值
既然是通过数组实现的,会获取ThreadLocal对象的哈希值并对数组长度取余来确定数组的索引下标,那么就可能出现哈希冲突,ThreadLocalMap是通过开放地址法来解决哈希冲突的。
内存泄漏
1. 第一种
上面提到了弱引用,这意味着,如果ThreadLocal没有被强引用的话,Entry中的k就会被GC会收掉,但是Entry中的value因为是强引用不会被回收掉。
那么 相当于有些Entry中k为null的value无法被访问到,如果线程不结束或者长时间不结束(比如使用线程池),而软引用被GC了,久而久之就会导致k为null的Entry越来越多,但是不会被使用,从而造成内存溢出
不过不需要担心,因为ThreadLocalMap中的set/get方法中用清空k为null的Entry的功能,因此忽略这种情况的
2. 第二种
ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key(这里指的是ThreadLocal对象对应数组Entry元素)就会导致内存泄漏,而不是因为弱引用。
有这种说法,因为线程不销毁导致持有ThreadLocalMap对象不销毁,往里面添加很多key(ThreadLocal)导致内存泄漏
但是一个应用里面怎么会有那么多ThreadLocal对象呢?除非ThreadLocal不是静态的,线程没有销毁,很多地方每次都创建新的ThreadLocal然后使用,并且最后也没有remove掉。
通常我们使用ThreadLocal都定义成静态变量,从而防止第一种情况的弱引用被GC回收而获取不到Entry对应的value值
最终解决内存泄漏问题:
使用完了调用remove()方法移除ThreadLocalMap中的ThreadLocal对象
局限
ThreadLocal只能是自己线程存储和取值,子线程无法获取父线程存储的值
因此,出现了InheritableThreadLocal,不仅有ThreadLocal功能,还能获取父线程中存储的值
InheritableThreadLocal
源码分析
基本与ThreadLocal一样,新增了子线程获取父线程存储的数值功能
继承ThreadLocal并重写了三个方法
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals; //使用的是线程对象的inheritableThreadLocals属性
}
void createMap(Thread t, T firstValue) { //这个方法有用吗???感觉没用上
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
Thread新建的时候初始化操作
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
如果父线程中的inheritableThreadLocals不为空,那么新建,如何新建见下文:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
基于父线程中的ThreadLocalMap新建当前线程的inheritableThreadLocals属性
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++;
}
}
}
}
局限
从上述分析可以看到两个局限:
1. 在子线程中只能获取到新建子线程时候父线程存储的值
2. 子线程新建完成后,之后父线程的的设置、删除与子线程无关,子线程的的设置、删除与父线程无关
NamedThreadLocal
继承自ThreadLocal,新增了name属性
NamedInheritableThreadLocal
继承自InheritableThreadLocal,新增name属性