ThreadLocal简介
ThreadLocal是java中将非线程安全变为线程安全的一个神器.通过为每一个线程保存一个线程本地变量来保证数据的安全性,通过set()和get()方法来使用. 因为每个线程都拥有变量的副本.不和其他线程变量交互,所以不会出现线程安全问题.所以这里有一个点就是ThreadLocal应用的场景应该是变量的访问是没有依赖关系的,每个线程只和自己的数据副本打交道.
属性
1 | public class ThreadLocal<T> { |
主要方法
get()
1 | public T get() { |
看代码可以知道首先获取当前线程,然后通过getMap获取到一个ThreadLocalMap.如果当前线程存在map的话,就从map中取出当前线程所需要的值.注意这里getEntry中传入的是this,不是当前线程t.因为map中存储的是键值对是ThreadLocal变量与ThreadLocal变量存储的值的对应.
接下来看getMap方法
getMap()
1 | ThreadLocalMap getMap(Thread t) { |
这里就是获得线程自己的ThreadLocalMap.每个线程都有一个ThreadLocal变量.在初始的时候是空的.1
2// 这是线程中的ThreadLocalMap变量
ThreadLocal.ThreadLocalMap threadLocals = null;
然后上面获取到的.如果获取到的map不是空的,说明已经设置过本地变量.因此就直接获取到此map的键值对.然后返回所要的值.
如果map为空,就通过setInitialValue()方法设置初始值.
setInitialValue()
1 | private T setInitialValue() { |
这个代码也很清晰明了了,如果当前线程有本地map,就直接添加默认值,如果没有,就使用createMap()创建一个
createMap()
创建Map其实就是给当前线程的ThreadLocal赋值.1
2
3void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
接下来细看一下ThreadLocalMap类的内部实现
ThreadLocalMap
在这里map中的Entry存储的key是一个ThreadLocal变量,值就是设置的值.这里有一点要搞明白.每个线程都有一个ThreadLocalMap的变量.通过这个变量能够找到当前线程存储的每个的ThreadLocal所对应的值.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19static class ThreadLocalMap {
//这里在说一下,这里使用的是弱引用.是为了让在线程将此值释放掉后能够让虚拟机进行回收,当线程本地释放变量的时候,不需要在对map进行主动移除,虚拟机就会自动回收掉无用变量.
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//省略一些基本的map的操作与字段,里面也是使用数组来存储数据.使用线性探测来解决hash冲突.
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);
}
重点看一下getEntry,通过得到当前线程保存的ThreadLocal变量.然后在通过这个值找到对应表中的位置,在这里有一点可能会出现为空或者这个key值不是当前线程保存的本地ThreadLocal的变量的情况.通过在线性探测法遍历整个数组. 这里就有个问题了,在线性探测法中,如果自己原本的位置有数据,然后自己向推了一格.但是之后前面的节点又被删除了,这个时候就会出现找到的位置节点是空的情况.
在说线性探测法的缺陷改进之前,先看一下ThreadLocal的删除操作.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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// 调用map的remove操作
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// for循环.向后遍历找到,如果碰见空,则直接返回.如果遇见自己,则执行删除操作.
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
// 上面看到删除节点时候会调yoga此方法.
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 先将数组中的两个值键值对设置为空,
tab[staleSlot].value = null;
tab[staleSlot] = null;
// 设置size--
size--;
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
// 如果此ThreadLocal为空,则删掉此项值
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
上面这个方法大概意思就是在删除某个节点的时候,会判断在他后面的节点有多少个可以向前移动,如果可以向前移动,就向前移动(那些经过二次探测或者三次探测的节点才有可能向前移动). 直到碰见一个空节点位置.
删除讲完了,对于线性探测法的缺点的改进也就很容易明白了,不会出现那种情况.因为每一次删除一个节点.都会将之后的连续的不为空的节点向前移(这里向前移动最多移动到自己节点的hash值与上len值的位置).也就是把删除掉的那些空位给补上.
set()
1 | public void set(T value) { |
这里set方法首先就获取当前线程,在获取一个ThreadLocalMap(),然后设置值
总结
到这里可能会有一些人对这个ThreadLocal还有点懵逼,通过一个完整的例子来看一下.1
2private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static final ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
比如说这里面有两个ThreadLocal,上面讲过,线程中存储的是一个ThreadLocalMap,map中存储的是ThreadLocal变量与值的映射.比如这里,线程中的threadLocalMap中会有两对键值对,一个是threadLocal的,另一个是threadLocal1的.当然,拥有这两个键值对的前提是调用了threadLocal.set()方法或者是get方法.如果调用了threadLocal.set(str)方法,那么就会在此线程的map中添加一个映射,就是threadLocal->str的映射.如果在获取的时候,通过获取线程的threadLocalMap,找到此threadLocal对应的值,就是此线程保存的这个threadLocal的值了.
ThreadLocal的实现原理主要就是通过每个线程保存一个ThreadLocalMap的变量,在ThreadLocal中保存的ThreadLoca => value 的键值对映射的一个map,通过找到线程对应的threadlocalmap,然后在从threadlocalmap中找到对应的threadlocal对应的值,就能够获取到对应的变量副本.