虽然说每个线程访问到的ThreadLocal对象是同一个,但是在同一个ThreadLocal对象中会给每一个线程维护自己单独的ThreadLocalMap,然后ThreadLocalMap中会维护一个Entry数组,然后将当前ThreadLocal对象的弱引用作为key,需要存储的值作为value。存到Entry中,为什么可以保证线程安全呢,就是因为每个线程的ThreadLocalMap都不一样,所以在进行get、set时操作的Entry数组也不一样,自然就做到了不同线程直接的隔离,从而保证了线程安全。
需要注意:每个ThreadLocal只能set一个对象,Entry数组存储的不是同一个ThreadLocal对象set的不同值,而是存储的不同的ThreadLocal对象set的值。
ThreadLocal中有四个方法:
- initialValue():就是返回一个初始值null
- get():首先会拿到当前线程,然后根据当前线程去拿到对应的ThreadLocalMap。ThreadLocalMap中会维护一个Entry类型的数组,Entry其实就是一个key-value形式的数据结构。如果不为空,则根据当前的ThreadLocal对象计算对应的哈希值后去Entry数组中拿到对应的Entry对象,然后将里面存储的value强转为泛型T然后返回。如果Entry中value为空或者map为空,都会调用setInitialValue()方法进行初始化,初始化一个map并且设置value为null进行返回。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
- set():同样,先根据当前线程拿到对应的ThreadLocalMap对象,如果map为null则创建一个map并存储对应的Entry对象,如果存在map,则进行set操作,具体的set流程是这样的:先获取到Entry数组,并计算当前key的索引,然后遍历Entry进行查找,然后找到了对应的Entry对象则更新value并返回,如果没找到(可能是已经过期或者被回收)则重新创建一个Entry对象并插入数组中。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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();
}
- remove():先获取当前线程的ThreadLocalMap,然后计算需要remove掉的Entry的索引,然后清除掉对应的Entry后对Entry数组进行一些必要的调整
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;
}
}
}