ThreadLocal实例为每一个访问它的线程(即当前线程)都关联了一个该线程的线程特有对象
线程特有对象(TSO,Thread Specific Object):各个线程创建各自的实例,一个实例只能被一个线程访问的对象就被称为线程特有对象,相对应的线程就被称为该线程特有对象的持有线程
static ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
//ThreadLocal内部的初始化方法
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
Thread[] t= new Thread[5];
for (int i = 0; i < 5; i++) {
t[i]=new Thread(()->{
int num=local.get();
local.set(num+=5);
System.out.println(Thread.currentThread().getName()+" num="+num);
});
t[i].start();
}
}
首先,我们看一下ThreadLocal的所有方法:
其中红色方框标注的是比较常用的方法。
咱们先看set(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);
}
通过Thread.currentThread();获取当前线程,然后通过当前线程去获取当前线程的ThreadLocalMap ,也可以理解为ThreadLocalMap 就是线程的特有对象。
再看看ThreadLocalMap 是如何创建的?
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
这个this就是ThreadLocal实例,再接着往下看
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
ThreadLocalMap有一个Entry对象数组,而且这个entry是一个(K,V)结构。通过ThreadLocal实例的hashcode值&(数组容量值(INITIAL_CAPACITY) - 1)获得下标。
我们画个图理解一下:
接下来,我们分析一下当ThreadLocalMap不为空的时候,set()方法是如何赋值的
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) {
//替换脏的Entry
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
通过ThreadLocal> key拿到Entry 数组的下标,然后通过for循环去线性探测,避免hash碰撞冲突
线性探测:属于开放寻址发。而hashMap的底层是使用了链寻址法,即将相同hash值对象组成一个链表
通过下标获得entry对象,然后循环当前下标之后的的entry直到entry为null。通过当前entry拿到k:
接下来,我们来分析一下它是如何处理这些脏对象的
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;
// Start expunge at preceding stale entry if it exists
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
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);
}
这里有两个循环,第一个循环是以当前下标开始,向前循环,直到entry == null,找到最前的一个脏对象(key==null&&value!=null)的下标,并将下标赋值给slotToExpunge
第二个循环是以当前下标开始,向后循环,也就是线性探测,直到entry == null,获取当前entry的k:
当k == key时,替换value的值e.value = value,然后把脏对象替换当前entry对象
tab[i] = tab[staleSlot],即当前对象成了脏对象。
这是为了防止当用一个ThreadLocal进来放数据的时候放到了以前脏数据的位置,从而导致了同一个ThreadLocal有两个entry的问题。
接着判断slotToExpunge 有没有改变。如果没有改变,则将当前下标赋值给slotToExpunge,最后将脏数据清理掉,即value=null,最后执行完毕,退出当前方法;
当k==null && slotToExpunge == staleSlot,也就是当前entry前后的entry对象都是null,将当前下标赋值给slotToExpunge
最后,就剩下没找到相同的key,则将当前entry清空,重新生成一个entry,插入到当前下标下,同时清理其他脏对象
接下来,我们在来分析get()方法。
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;
}
get方法就简单的多了,通过当前线程Thread.currentThread(),得到该线程的ThreadLocalMap
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;
}
}
}
找到所有相同的ThreadLocal,然后将entry清空