应用场景
最近用到ThreadLocal,如下面方法调用 A -> B-> C -> … -> Z。此时可能会需要在Z获取A方法中的某个状态s,并且整个调用链中,s可能随时改变。如果通过修改接口的方式,可能改动较多,这个时候就可以尝试用ThreadLocal。
public class ThreadLocalAssistor {
private static final ThreadLocal<Integer> STATE = new ThreadLocal<Integer>();
}
public void A(int a, int b) {
ThreadLocalAssistor.STATE.set(1);
}
public void Z(String str) {
Integer state = ThreadLocalAssistor.STATE.get();
}
这样是不是很简单
ThreadLocal
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
public class ThreadLocal<T> {
// 获取线程的threadLocals变量
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public void set(T value) {
// 获取当前线程并获取其threadLocals变量
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// threadLocals不为空则设置值,否则为此线程创建threadLocals
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 这里道理和get相同
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
}
总结:ThreadLocal本身不存储任何数据,而是去提供一个途径去获取线程的ThreadLocalMap,并对其进行值设置
ThreadLocalMap
static class ThreadLocalMap {
private Entry[] table;
// 其Entry并没有像HashMap那样是链表形式,只是普通的存储K,V
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
private void set(ThreadLocal key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 从第一个i开始,如果重新进入则会调用到tab中的下一个,形成一个环状的查询过程
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal k = e.get();
// 如果为当前entry的ThreadLocal则修改其值
if (k == key) {
e.value = value;
return;
}
if (k == null) {
// 如果k为空,表示对应的ThreadLocal已经被gc清理了,则替换掉,防止内存泄露
replaceStaleEntry(key, value, i);
return;
}
}
// 如果没有,则在此位置新建一个Entry
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
// 如果到达table最后一个,让其返回0,从头再来
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
}
总结:Entry继承WeakReference并指定ThreadLocal为弱引用,所以gc的时候会清理没有被引用的ThreadLocal,但是对应Entry则不会清理。如果不这么做,当一个Thread调用多个ThreadLocal的set方法,而将这些ThreadLocal = null后,那么上面方法中 if (k == null) 的情况是不会出现的,因为引用关系如下 ThreadLocal -> Entry -> ThreadLocalMap -> Thread,所以Entry中的value会一直保留,会导致内存泄露。
InheritableThreadLocal
InheritableThreadLocal可以获取父线程中的ThreadLocal的值
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);
}
}
可见InheritableThreadLocal继承于ThreadLocal,并且操作线程的inheritableThreadLocals对象
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
// ......
Thread parent = currentThread();
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// ......
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
总结:线程创建的时候会获取父线程的inheritableThreadLocals,并以此创建自己的inheritableThreadLocals,来达到数据传递的作用。并且子线程在初始化获取父线程的数据后,后续对inheritableThreadLocals的操作都是他们独立了。
public static void main(String[] args) throws InterruptedException {
final InheritableThreadLocal<String> local = new InheritableThreadLocal<>();
local.set("1");
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("in "+ local.get());
local.set("2");
System.out.println("in set "+ local.get());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("in "+ local.get());
local.set("4");
System.out.println("in set "+ local.get());
}
}).start();
Thread.sleep(100);
local.set("3");
System.out.println("main set "+ local.get());
Thread.sleep(100);
System.out.println("main get "+ local.get());
}
// 结果如下:
in 1
in set 2
main set 3
in 2
in set 4
main get 3
// 对应的引用关系如下,只有继承的时候引用了父线程的值,后面的操作两者就断开联系了
父线程 -> 1 -> 3
^
|
子线程 - > 2 -> 4