举例:ThreadLocal实例的生命周期跟随方法。
原因:
Thread、threadLocals、ThreadLocal 的关系:
结论:
public class ThreadLocalTest {
public static void main(String[] args)throws Exception{
Thread thread = Thread.currentThread();
ThreadLocalTest test = new ThreadLocalTest();
test.test1();
System.gc();//test1执行完出栈,test1方法中的 threadLocal1、threadLocal2 指向的对象,gc时会被回收
TimeUnit.SECONDS.sleep(1);
System.out.println(thread);//debug 观察当前线程的 threadLocals 属性值:1、2对应的entry的key变成null
}
//entry的key为什么用弱引用?
//若不用弱引用,会导致方法执行完 threadLocal1、threadLocal2,无法被gc
//因为,Entry实例的key指向这个ThreadLocal实例
public void test1(){
ThreadLocal threadLocal1 = new ThreadLocal();
threadLocal1.set(1);
ThreadLocal threadLocal2 = new ThreadLocal();
threadLocal2.set(2);
Thread thread = Thread.currentThread();
System.out.println(thread);//debug 观察当前线程的 threadLocals 属性值:1、2对应的entry的key有值
}
}
Thread 类的成员变量 threadLocals 的类型是 ThreadLocal.ThreadLocalMap。
ThreadLocalMap类是 ThreadLocal 类的静态内部类。
ThreadLocalMap底层数据结构是 Entry[] 数组。
ThreadLocal threadLocal1 = new ThreadLocal();
threadLocal1.set(1);
threadLocal1.set(1); 就是向,执行这行代码的线程 的 threadLocals属性,put(threadLocal1,1)。 也就是向 Entry[] 数组添加 Entry(threadLocal1,1) 对象。
Entry类存一对键值对,key是 ThreadLocal 对象的弱引用,value是 Object对象的强引用。
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
开发中一般用线程池管理线程,核心线程会一直运行。那么核心线程的 threadLocals 属性指向的 ThreadLocalMap 对象会一直存在,ThreadLocalMap 底层的 Entry[] table 数组也会一直存在。
直接看更新源码:
1、更新时,会先遍历 Entry[] 数组,判断key是否存在,存在则覆盖value。
2、若Entry[] table数组 不存在此key的Entry对象,则创建新的entry对象插入Entry[] table数组。
所以,更新方法不会操作key为null的Entry对象。
//修改当前线程的 ThreadLocalMap
ThreadLocal threadLocal = new ThreadLocal();
threadLocal.set(1);
//ThreadLocal 部分源码
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);//不存在,创建ThreadLocalMap对象,再更新
}
}
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)]) { //遍历Entry[]数组
ThreadLocal> k = e.get();
if (k == key) { //比较key是否存在,存在则覆盖value
e.value = value;
return;
}
if (k == null) { //这是针对内存泄漏的解决方法
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);//不存在,创建新的Entry对象添加到数组中
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold) //判断数组长度是否要扩容
rehash();
}
ThreadLocal 类的 set 方法中,判断key为null,则将Entry[] table数组对应的索引指向null。
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();
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();
}