理解ThreadLocal
理解ThreadLocal的关键在于理解 Thread, threadLocals, ThreadLocal 三者的关系
- threadLocals是Thread中持有的一个实例变量 ThreadLocal.ThreadLocalMap,Thread持有该变量,但是它却是由ThreadLocal负责管理(get/set/remove 方法)其本质上是一个定制化的hash map
1
2
3
4
5
6
7
8
|
public class Thread implements Runnable
{
...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
|
- ThreadLocal 可以理解为线程变量,用来绑定线程与资源(状态),其中最重要的方法是get/set/remove
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
|
public class ThreadLocal<T>
{
...
public T get() {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取当前线程的threadLocals
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 从threadLocals map中获取绑定在当前线程的资源
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
...
}
|
ThreadLocal的使用场景
- 解决对象的实例变量导致的线程不安全问题
由于ThreadLocal本质上将实例变量进行了拷贝,并与线程绑定,实现了资源的线程隔离,所以不存在实例变量的共享问题,自然也没有线程不安全的问题。这里需要注意的一点是,ThreadLocal 自身在不同线程中是同一个对象,但是我们从中获取的资源却是属于各自不同的线程的。一个恰当的比喻是:Thread比作不同的公交线路,threadLocals就是行驶在不同线路上的公交车- 用来运输该条线路的乘客(资源),而ThreadLocal则是公交线路上的乘车点。虽然同一个乘车点可能会有多条的线路共用,但是同一时间上下车的人仍然属于各自的公交线路。
- 实现同一线程内的数据传递
ThreadLocal的内存泄露问题
TODO
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
|
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CountDownLatch;
@Slf4j
public class ThreadLocalTest
{
@Test
public void testThreadLocal() throws InterruptedException
{
CountDownLatch latch = new CountDownLatch(2);
/*
Junit中测试多线程,主线程结束之后会直接结束,不会等待子线程结束
解决办法:子线程没结束之前阻塞主线程
1. 调用join方法,等待子线程结束
2. 使用CountDownLatch 多线程倒计时计数器,确保一组任务执行完成
******************************************************
1 创建CountDownLatch并设置计数器值。
2 启动多线程并且调用CountDownLatch实例的countDown()方法。
3 主线程调用 await() 方法,这样主线程的操作就会在这个方法上阻塞,
直到其他线程完成各自的任务,count值为0,停止阻塞,主线程继续执行。
******************************************************
*/
//ThreadLocal是同一个对象,但是通过get()方法得到的是绑定在各自线程上的ThreadLocalObj对象
ThreadLocal<ThreadLocalObj> obj = ThreadLocal.withInitial(ThreadLocalObj::new);
Thread t1 = new Thread(() -> {
log.info("t1 running=======");
log.info(Thread.currentThread().getId() + obj.get().getObjStr());
log.info("+++++++++++++" + obj);
log.info("+++++++++++++" + obj.get());
obj.get().setObjStr("t1 changeId");
log.info(Thread.currentThread().getId() + obj.get().getObjStr());
log.info("+++++++++++++" + obj);
log.info("+++++++++++++" + obj.get());
latch.countDown();
});
Thread t2 = new Thread(() -> {
log.info("t2 running=======");
log.info(Thread.currentThread().getId() + obj.get().getObjStr());
log.info("=============" + obj);
log.info("=============" + obj.get());
latch.countDown();
});
t1.start();
// t1.join();
t2.start();
latch.await();
// t2.join();
}
@Data
static class ThreadLocalObj
{
String objStr = "ThreadLocalObj";
}
}
------ output ------
21:59:31.218 [Thread-3] INFO ThreadLocalTest - t1 running=======
21:59:31.218 [Thread-4] INFO ThreadLocalTest - t2 running=======
21:59:31.239 [Thread-3] INFO ThreadLocalTest - 17ThreadLocalObj
21:59:31.239 [Thread-4] INFO ThreadLocalTest - 18ThreadLocalObj
21:59:31.244 [Thread-3] INFO ThreadLocalTest - +++++++++++++java.lang.ThreadLocal$SuppliedThreadLocal@5923c36c
21:59:31.244 [Thread-4] INFO ThreadLocalTest - =============java.lang.ThreadLocal$SuppliedThreadLocal@5923c36c
21:59:31.250 [Thread-4] INFO ThreadLocalTest - =============ThreadLocalTest.ThreadLocalObj(objStr=ThreadLocalObj)
21:59:31.250 [Thread-3] INFO ThreadLocalTest - +++++++++++++ThreadLocalTest.ThreadLocalObj(objStr=ThreadLocalObj)
21:59:31.252 [Thread-3] INFO ThreadLocalTest - 17t1 changeId
21:59:31.252 [Thread-3] INFO ThreadLocalTest - +++++++++++++java.lang.ThreadLocal$SuppliedThreadLocal@5923c36c
21:59:31.252 [Thread-3] INFO ThreadLocalTest - +++++++++++++ThreadLocalTest.ThreadLocalObj(objStr=t1 changeId)
|