ThreadLocal使用

ThreadLocal是一个以当前线程为key,存储变量的map容器,能够解决线程安全问题。

首先我们先看下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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
public class ThreadLocalDemo {

ThreadLocal<Integer> count = ThreadLocal.withInitial(() -> new Integer(0));

public int getNext() {
Integer value = count.get();
value++;
count.set(value);

return value;

}

public static void main(String[] args) {
new Thread(() -> {
ThreadLocalDemo demo = new ThreadLocalDemo();
while (true) {
System.out.println(Thread.currentThread().getName() + " " + demo.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();

new Thread(() -> {
ThreadLocalDemo demo = new ThreadLocalDemo();
while (true) {
System.out.println(Thread.currentThread().getName() + " " + demo.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
ThreadLocalDemo demo = new ThreadLocalDemo();
while (true) {
System.out.println(Thread.currentThread().getName() + " " + demo.getNext());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();

}
}


```

打印结果如下:

![ThreadLocal](/img/assets/53/04.png)

从上面的结果可以看出,ThreadLocal对应的变量可以被线程单独访问,能够避免线程安全问题。

为什么ThreadLocal能够解决线程问题呢?我们先看下ThreadLocal的get方法:

``` Java

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();
}
//getMap(t)的方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
```

看到这个方法,我总觉的这段代码写的有问题,好像和自己写的代码差不多。。。(JDK源码的水平也有差的)


![ThreadLocal](/img/assets/53/01.png)


ThreadLocal和Thread的关系如下:

![ThreadLocal](/img/assets/53/02.png)


当看到这个引用图的时候,隐隐觉得会有问题,在当前的线程中,如果线程不消亡,那`ThreadLocalMap` 的map实例就永远不会被回收。

因为在GC每次计算对象还有没有引用的时候, 判断永远是有在用用。这样程序中set的值就永远存在,这样就导致了内存的泄露。

在线程池中,由于线程的`复用性`,就容易造成`ThreadLocal`内存泄露的问题,所以最好在线程执行完成后,调用remove手工删除数据。


对于上面的这个问题,jdk也有对应的优化,具体的方法是,`ThreadLocalMap`使用弱引用(`对象为空的时候会释放`)但优化的范围有限,具体的引用关系如下:


![ThreadLocal](/img/assets/53/03.png)


上面的优化过程,如果ThreadLocal被置空,则LocalThreadMap只会有`弱引用`,原来的`ThreadLocal对象(为了方便,称为对象A)`也会在下次直接被GC回收,所以在下次GC的时候, 对象A的引用会成为null.

但这样在`LocalThreadMap`中会带来一个问题,这样导致了在Key-Value中,key的引用的地址的值成为了null,这样也就导致了value无法被找到,也会造成内存泄露。

在`LocalThreadMap`中在每次`get()/set()/remove()`操作map的中的值的时候,会自动清理key为null的值,这样就能够避免了泄露的问题。具体的源码如下:

``` Java
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);
}

/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
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;
}
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;

// expunge entry at staleSlot
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;

// Rehash until we encounter null
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;

// Unlike Knuth 6.4 Algorithm R, we must scan until
// null because multiple entries could have been stale.
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}

ThreadLocal多少还是有点风险,除非明确能够使用和删除ThreadLocal的值,否则尽量还是慎用。

作者

付威

发布于

2019-05-01

更新于

2019-05-01

许可协议

评论