咋个使用?
ThreadLocal<String> localName = new ThreadLocal();
localName.set("xx");
String name = localName.get();
原理
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
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);
}
}
首先看set方法,实际上value是存在ThreadLocalMap的,Thread类自带ThreadLocalMap成员
【也就是一个线程有一个map专门用来存threadlocal对象作为key的数据,解决hash冲突是用开放地址法】,
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) //开放地址
可以看到ThreadLocal是一个弱引用,那么当GC线程扫描的过程中一旦发现某个对象只具有弱引用而不存在强引用时不管当前内存空间足够与否GC都会回收它的内存。【为了防止内存溢出,在处理一些占用内存大而且生命周期较长的对象时候,可以尽量使用软引用和弱引用。】
【理解弱引用例子:Java弱引用WeakReference理解,这一篇就够了!_程冯冯的博客-CSDN博客_继承弱引用】
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
内存泄漏?
// 有强引用指向ThreadLocal对象
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("abc");
// 没有强引用指向ThreadLocal对象
new ThreadLocal<>().set("def");
没有强引用存在时,弱引用指向的对象会被垃圾回收器回收。不过,这里key虽然被回收了,但是value依然会出现内存泄漏问题。只有当线程生命周期结束,或者触发清理算法时,value才能被gc回收。
如果此时,我们ThreadLocal对象也是一个静态常量,那么在下一次线程被使用的时候,很可能获取到的是之前保存的数据,导致脏数据。
如何避免内存泄漏?
及时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;
}
}
}
神奇的 0x61c88647
使得hash 分布非常均匀。【ThreadLocal 面试看这一篇就够了_晓呆同学的专栏-CSDN博客】
int h = key.threadLocalHashCode & (len - 1);
要是新来一个数要算其hash值,key.threadLocalHashCode是在原来基础上加了0x61c88647的
InheritableThreadLocal?
使用ThreadLocal时,子线程获取不到父线程通过set方法保存的数据,要想使子线程也可以获取到,可以使用InheritableThreadLocal类。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
原理超级简单,当调用set时,实际是往Thread类的ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;? 这里面写的,为啥子线程可以拿到?
public class Thread implements Runnable {
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) {
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}}
子线程中数据是从父线程拷贝来的,所以,在子线程中重新set的内容,对于父线程是不可见的。
可以参考的其他资料
Java面试必问,ThreadLocal终极篇 - 简书
面试题:由ThreadLocal引发的惨案_zuihongyan518的博客-CSDN博客_threadlocal 面试
|