ThreadLocal对于每个线程都创建一个ThreadLocalMap副本,相当于是以空间换取时间实现的线程安全策略,而synchronized(加锁)相当于以时间换取空间实现线程安全。
ThreadLocal.set();
源码
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//根据thread获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//map已经生成的话,则替换值。
map.set(this, value);
else
//map未生成,则创建map并放入value值。
createMap(t, value);
}
从set方法我们可以看出来,实际上先根据线程来获取到ThreadLocalMap,然后再根据ThreadLocal来获取或者设置具体的value值置于ThreadLocalMap中。
createMap方法
void createMap(Thread t, T firstValue) {
//实际上threadlocalMap是Thread类中的threadLocals字段属性。我们后续可以直接通过反射获取ThreadLocalMap。
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
new ThreadLocalMap(this, firstValue)方法
private Entry[] table;
private static final int INITIAL_CAPACITY = 16;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
//同HashMap获取数组下标相同。
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//Entry数组
table[i] = new Entry(firstKey, firstValue);
size = 1;
//设置扩容阈值
setThreshold(INITIAL_CAPACITY);
}
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
//Entry对象
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
结合以上可知,ThreadLocal实际上将值存放在ThreadLocalMap中,而ThreadLocalMap实际上是将值存放在Entry数组中,而Entry对象实际上是WeakReference弱引用的子类,而Entry实际上同ThreadLocal关联起来,并且将值存放在其中的value字段属性中。
ThreadLocal.get()
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//根据ThreadLocal获取Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//返回Entry中存放的value
T result = (T)e.value;
return result;
}
}
//实际上是返回null,方法会判断ThreadLocalMap是否生成,未生成则初始化ThreadLocalMap,并注入null值。
return setInitialValue();
}
ThreadLocalMap.getEntry()
private Entry getEntry(ThreadLocal<?> key) {
//获取位于对应数组的下标
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//如果ThreadLocal相同,则返回该Entry。
if (e != null && e.get() == key)
return e;
else
//同HashMap中可能产生相同的数组下标对应,如果ThreadLocalMap不相同,则以该下标为起点从前往后找(直到为null为止)。
return getEntryAfterMiss(key, i, e);
}
Entry.get实际上是Reference.get,Reference是个抽象类,由WeakReference类(弱引用)实现。
public T get() {
//即referent存放的就是ThreadLocal的对象地址
return this.referent;
}
即get方法是获取弱引用的ThreadLocal,判断是否相同,相同则取Entry中的value值返回。
总结get和set方法,即ThreadLocal实际上是将值存放在ThreadLocalMap中,而ThreadLocalMap实际上是Thread类的字段,而ThreadLocalMap将值存放在Entry数组中,而Entry数组又是以ThreadLocal关联上value,这也是为何Threadlocal是与Thread相关联的了。
下面就可以解释下为何ThreadLocal推荐设置成static,因为它本身就是线程安全的类,而且Entry的value值的获取与ThreadLocal有关。因为目前Spring框架默认的Bean是单例,因此ThreadLocal不设置成static可能没问题,但是一旦设置成原型,那么就会发生很多ThreadLocal的创建与回收,这是可以避免的。而千万要注意的是:不要在局部方法中创建使用ThreadLocal,举例如下:
public class Test {
public static void main(String[] args) throws Exception {
Test test = new Test();
test.loop();
//建议jvm进行gc操作
System.gc();
System.out.println("--触发GC操作--");
test.printEntryInfo();
}
public void loop() throws Exception {
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
threadLocal.set(new Integer(555));
printEntryInfo();
}
private void printEntryInfo() throws Exception {
Thread currentThread = Thread.currentThread();
Class<? extends Thread> clz = currentThread.getClass();
//上面讲述源码时介绍过,实际上ThreadLocal将值存放在Thread的threadLocals字段中。
Field field = clz.getDeclaredField("threadLocals");
field.setAccessible(true);
Object threadLocalMap = field.get(currentThread);
Class<?> tlmClass = threadLocalMap.getClass();
Field tableField = tlmClass.getDeclaredField("table");
tableField.setAccessible(true);
//即获取Entry[]数组。
Object[] arr = (Object[]) tableField.get(threadLocalMap);
for (Object o : arr) {
if (o != null) {
Class<?> entryClass = o.getClass();
//获取Entry中的value
Field valueField = entryClass.getDeclaredField("value");
//获取referent,实际上referent存放ThreadLocal对象地址。
Field referenceField = entryClass.getSuperclass().getSuperclass().getDeclaredField("referent");
valueField.setAccessible(true);
referenceField.setAccessible(true);
System.out.println(String.format("key:%s,值:%s", referenceField.get(o), valueField.get(o)));
}
}
}
}
打印如下
可以看出,如果在局部方法中创建并使用ThreadLocal,一旦局部方法的生命周期结束后,那么在此阶段如果触发了GC操作,那么ThreadLocal对象会被回收(因为Entry中的ThreadLocal为弱引用),即Entry的referent会为null,但是由于value被Entry的value引用,而ThreadLocalMap被Thread引用,那么会导致该value一直无法被回收,直到Thread即线程结束为止。
为何要手动remove?
如果框架中的线程是采用线程池技术的话,那么可能会导致不同会话实际是复用一个线程,即第二个会话可能会获取到第一个会话的信息(这可能是不正确的,如果使用ThreadLocal存放用户信息的话),因此推荐ThreadLocal在使用完成后,都调用ThreadLocal.remove方法,并且将ThreadLocal设置成static。
ThreadLocal.remove()方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
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) {
//将Entry中的referent置空。
e.clear();
//将Entry的value置空,并且将引用该Entry的地方置空。
expungeStaleEntry(i);
return;
}
}
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// expunge entry at staleSlot
//主要逻辑在这两行代码
//将Entry的value置空,并且将引用该Entry的地方置空。
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
//调整Entry数组
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
public void clear() {
//将Entry中的referent置空。
this.referent = null;
}