ThreadLocal使用场景
ThreadLocal是绑定在Thread上的变量,生命周期与Thread相同。通常用于线程内数据共享。
例如线程内接口调用追踪,我们会在第一个调用处生成一个ID,在接下来的接口中,获取这个ID,打印出调用链,那么就可以用ThreadLocal很方便的传递这个调用链ID,而不影响其他线程的调用链ID的传递与生成。
源码解读
我们通常使用ThreadLocal的set,get和remove方法。
set处理代码片段:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//map不为空,则将当前变量与当前线程绑定
if (map != null)
map.set(this, value);
else
//为空,则创建map
createMap(t, value);
}
接下来看看createMap,源码如下
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化一个entry数组
table = new Entry[INITIAL_CAPACITY];
//计算所在位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//构造entry,并放入相应的位置
table[i] = new Entry(firstKey, firstValue);
//设置当前table元素个数
size = 1;
//设置容量调整阈值
setThreshold(INITIAL_CAPACITY);
}
若当前线程中有ThreadLocalMap,则将变量塞到map中去。源码如下:
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//循环获取entry
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//若变量已存在且没被gc,则更新值
if (k == key) {
e.value = value;
return;
}
//找到位置了,删除无用的entry,将该entry放入相应的位置
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//变量不存在,则放入map
tab[i] = new Entry(key, value);
//table的元素个数+1
int sz = ++size;
//是否需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
其中replaceStaleEntry方法如下,这是一坨复杂的逻辑,主要完成的就是清除过期的entry,然后将新的entry放入进来:
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// If we didn't find stale entry on backward scan, the
// first stale entry seen while scanning for key is the
// first still present in the run.
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// If there are any other stale entries in run, expunge them
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
至此,set完成。
接下来看get源码:
public T get() {
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//Map 不为空,遍历map,找到相应的值,
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//Map为空,或者map里没有相应的值,则取初始值
return setInitialValue();
}
每个线程持有的ThreadLocalMap,实际上操作的都是Map的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;
}
}
从源码中可以看出,线程本地变量的隔离机制如下:
每个线程Thread都会有一个ThreadLocalMap的成员变量。Map的key值是定义的ThreadLocal实例。所有的线程里Map的ThreadLocal都指向同一个地址,只是每个线程的value值不一样。即同一个变量在每个线程里的值都不一样。到达了线程隔离的效果。