并发编程中,另一个常用的工具是Threadlocal.今天来一探其神秘面纱。
我们最常用的是Threadlocal的set方法,下面从set方法的源码入手:
1
2
3
4
5
6
7
8
9
10
public void set ( T value ) {
Thread t = Thread . currentThread ();
ThreadLocal . ThreadLocalMap map = this . getMap ( t );
if ( map != null ) {
map . set ( this , value );
} else {
this . createMap ( t , value );
}
}
从上面的代码来看,先回调用getMap判断ThreadLocalMap是否存在,getMap的代码如下:
1
2
3
ThreadLocal . ThreadLocalMap getMap ( Thread t ) {
return t . threadLocals ;
}
它实际上就是Thread对象的一个成员变量。再回到set方法,如果当前Thread对象的threadLocals没有设置,则会调用createMap创建新的ThreadlocalMap。
1
2
void createMap ( Thread t , T firstValue ) {
t . threadLocals = new ThreadLocal . ThreadLocalMap ( this , firstValue );
将当前ThreadLocal对象和传给ThreadLocalMap。
下面来看看ThreadLocalMap的构造函数
1
2
3
4
5
6
7
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 );
}
ThreadLocalMap实际上只是一个数组,第一个entry的放入位置是根据firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)来计算的。ThreadLocal的threadLocalHashCode是0x61c88647累加得到的,而0x61c88647是黄金比例Math.sqrt(5) - 1左移31位得到。
再回到set方法,如果Thread的threadlocal已存在,则直接调用ThreadLocalMap的set,源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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 ();
}
set方法会先判断map中是否有同一个ThreadLocal,如果有就使用当前的value覆盖调原来的value。如果原来的threadlocal为null(被回收掉了),则直接使用当前的key/value替换掉原来的threadlocal。否则,找到一个空的位置把当前的Entry填进去。
下面看看get方法的源码
1
2
3
4
5
6
7
8
9
10
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 ();
}
get方法很简单就是从Thread的ThreadLocalMap中取,如果没有取到,则初始化数据到map中。
ThreadLocal的remove是直接调用ThreadLocalMap的remove方法,源码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 ;
}
}
从代码来看,如果找到则会调用Entry的clear,接着调用expungeStaleEntry移除entry。
通读源码知道,ThreadLocalMap的设计思想跟HashMap是不一样的,HashMap实用链表来解决冲突,而ThreadLocalMap实用是开放地址的算法来解决冲突,同时在set的时候会移除stale entry,来保证数组不会太慢,导致多次rehash。