ThreadLocal的三个理论基础
1. 每个线程都有一个自己的ThreadLocal.ThreadLocalMap对象,ThreadLocal类中定义了静态类ThreadLocalMap,
静态类ThreadLocalMap中定义了Entry结构存储
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
2. 每个ThreadLocal对象都有一个循环计数器
3. ThreadLocal.get()取值,就是根据当前的线程,获取线程中自己的ThreadLocal.ThreadLocalMap,然后在这个Map中根据根据第二点中循环计数器取得一个特定value值
两个数学问题
1. ThreadLocal.ThreadLocalMap规定了table的大小必须是2的N次幂
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
计算机处理位运算的效率比数学运算要高,例如ThreadLocalMap中获取Entry对象的方法
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);//1
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
上面第一行代码,通过与运算获取到Entry在数组中的索引
如果使用%的话,table.length=16,存在一个数字23,23%16=7,如果转为上面的二进制运算的话:
23 -> 00010111
&
table-1 = 15 -> 00001111
result: 00000111 就是十进制的 7 ,效率更高
2. 对于上面取模获取在table中索引位置时候,threadLocalhashCode源码如下:
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
private static final int HASH_INCREMENT = 0x61c88647;
length 为 16 和 32 时候,取模生成的值
通过取模方式获取索引的时候,每次都会在原来的threadLocalHashCode的基础上加上0x61c88647,这样的结果是生成的hash值分散,而且在length扩容为,2的n次幂,之后,生成的hash值会和前面扩容前的值一致,这就保证了threadLocalHashCode可以从任何地方开始。
set(T value)
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
1. 获取当前线程
2. 获取当前线程的ThreadLocalMap,不为null,就往里面设值
3. 否则,就去创建ThreadLocalMap,并设值
看下,第三步的源码:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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);
}
创建一个Entry数组,将初始值放入,取模运算后的索引位置,并且置entry数量为1,而且从ThreadLocalMap中看出并没有next节点,也就是ThreadLocalMap不是类似于hashmap的链表结构,而是开地址法,每次递增一个值,取模运算计算索引存放元素。这样的结果就是设置同一个value放到table中的位置会不一样的
接着,看下,ThreadLocalMap的设值源码:
private void set(ThreadLocal key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
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();
}
1. 取模运算获取到table数组的索引位置,从这个索引位置开始遍历到数组最后
2. 根据获取每个Entry的ThreadLocal引用,对于Entry它是个弱引用
static class Entry extends WeakReference<ThreadLocal> {
,获取到ThreadLocal
3. 如果key和k是指向同一个ThreadLocal,那么就将值设置到这个Entry上,返回
4. 不是同一个ThreadLocal,在判断下位置上的ThreadLocal是不是空的,因为Entry是弱引用,有可能这个ThreadLocal已经被垃圾回收了,如果ThreadLocal是空的,会去轮询找到下一个不为null的entry,将值放在这个entry,并且会去将过期的entry删除
5. 如果上面都没有返回的话,将entry数量加1 ,在索引位置设置一个新的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)
return (T)e.value;
}
return setInitialValue();
}
private Entry getEntry(ThreadLocal key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
1. 获取到当前线程绑定的ThreadLocalMap
2. 如果ThreadLocalMap不为空
* 如果根据index能直接找到Entry,并且不为null,直接返回这个Entry
* 否则,继续向下遍历,找到下一个不为null的entry,返回这个entry,并在轮询过程中将过期的entry删除
3. 如果ThreadLocalMap为空
给当前线程创建一个ThreadLocalMap,并设置初值
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) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
1 . 找到线程绑定的ThreadLocalMap,并且找到对应的entry,删除掉这个entry
总结
1. ThreadLocal不需要键值,因为ThreadLocalMap不是通过链表实现,而是通过开地址法实现的
2. 设置值的时候,如果index位置找到entry,并且key(这个key指的是ThreadLocal引用)是同一个,进行覆盖,否则向下找到一个不为null的entry,并设值
3. 查找数据的时候,如果能够从index找到entry,直接就返回了,否则就向下继续找到下一个不为null的entry,返回
4. 如果需要向ThreadLocal中存放不同类型的数据,需要定义多个ThreadLocal
参考博客:
http://www.cnblogs.com/xrq730/p/4854813.html