Android技术---ThreadLocal详解

前言

不管是平时开发,或者是阅读别人的代码关于多线程的时候。我们总会遇到这个ThreadLocal。
今天算是偶尔也和大家一起来说说Java基础的东西。
ThreadLocal从字面的意思来说其实就是一个线程局部变量,

情景

我们假想一个情景,有3个线程,A线程和B线程,还有我们的主线程。
有一个数字的对象在主线程里,然后A线程和B线程一起读取做一些操作

先画个图解释一下,再上代码

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
package com.martinhan.zeroone;
public class Num {
private int value;
public Num() {
super();
}
public Num(int value) {
super();
this.value = value;
}
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
public void addValue(int value) {
this.value += value;
}
}
package com.martinhan.zeroone;
public class ThreadLocalTest1 {
public static void main(String[] args) {
Num num = new Num(100);
Thread aThread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(100L);
num.addValue(200);
System.out.println("thread ---" + Thread.currentThread() + " value is " + num.getValue());
} catch (InterruptedException e) {
e.printStackTrace();
};
}
};
Thread bThread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(100L);
num.addValue(200);
System.out.println("thread ---" + Thread.currentThread() + " value is " + num.getValue());
} catch (InterruptedException e) {
e.printStackTrace();
};
}
};
aThread.start();
bThread.start();
}
}

执行结果如下

1
2
thread ---Thread[Thread-0,5,main] value is 300
thread ---Thread[Thread-1,5,main] value is 300

其实正常情况下,我们当然不希望是这种结果了。
A线程希望改完了之后,值是300
B线程希望改完了之后值才是500

ThreadLocal引入

此时此刻,我们就想,可不可以这样呢,我们自己做一个HashMap,key放线程的id,然后value放各个线程需要的值。
有了这个机制,我们就可以实现各个线程其实对应的实际Num对象并不是一个了。
用语言来讲的话可能还是不太形象,画个图

然后我们用代码来实现

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
package com.martinhan.zeroone;
import java.util.HashMap;
public class Num2 {
private HashMap<Long,Integer> map = new HashMap<Long,Integer>();
public Num2() {
super();
}
public Num2(int value) {
super();
this.map.put(Thread.currentThread().getId(), value);
}
public Integer getValue() {
return map.get(Thread.currentThread().getId());
}
public synchronized void setValue(int value) {
this.map.put(Thread.currentThread().getId(), value);
}
public synchronized void addValue(int value) {
if(map.containsKey(Thread.currentThread().getId()) == false) {
return ;
}
int oldvalue = map.get(Thread.currentThread().getId());
map.put(Thread.currentThread().getId(), oldvalue + value);
}
}
package com.martinhan.zeroone;
public class ThreadLocalTest2 {
public static void main(String[] args) {
Num2 num = new Num2();
Thread aThread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(100L);
num.setValue(100);
num.addValue(200);
System.out.println("thread ---" + Thread.currentThread() + " value is " + num.getValue());
} catch (InterruptedException e) {
e.printStackTrace();
};
}
};
Thread bThread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(100L);
num.setValue(100);
num.addValue(400);
System.out.println("thread ---" + Thread.currentThread() + " value is " + num.getValue());
} catch (InterruptedException e) {
e.printStackTrace();
};
}
};
aThread.start();
bThread.start();
}
}

执行结果如下

1
2
thread ---Thread[Thread-1,5,main] value is 500
thread ---Thread[Thread-0,5,main] value is 300

setValue,addValue方法做了线程同步,因为HashMap本身就是不安全的。这次的执行结果就对了。
按照了我们的图示,这次每个线程都操作自己的数据。然后不会有线程不安全的情况了。
可能有人说,加同步不就好了么,其实不是想为大家一起说下ThreadLocal嘛,举个例子

ThreadLocal

ThreadLocal其实做的事情就大概如此了,只是大概如此,如果需要深究细节,需要去仔细看源码。
以上的代码,还有几处不完善,那个HashMap里的对象会一直存在,他并不会随着线程的结束而删除。

在Java源码里,真正的map是在Thread里面的,

先举个例子

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
package com.martinhan.zeroone;
public class ThreadLocalTest3 {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
static int value = 100;
public static void main(String[] args) throws InterruptedException {
Thread aThread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadLocal.set(value + 200);
System.out.println("thread ---" + Thread.currentThread() + " value is " + threadLocal.get());
}
};
Thread bThread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadLocal.set(value + 400);
System.out.println("thread ---" + Thread.currentThread() + " value is " + threadLocal.get());
}
};
aThread.start();
bThread.start();
}
}

然后我们从threadLocal.set方法来读,实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取t线程对象的成员变量threadLocals,threadLocals是一个ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//最后如果map不为空的话,就放入
map.set(this, value);
}
else {
createMap(t, value);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

在线程退出的时候有如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}

这里面手动置空了threadLocals成员变量

源码地址

源码链接

写在最后

ThreadLocal大体的思路如此,个人觉得懂了两个图,就算可以说大致懂了,然后读源码
希望读者有问题,可以和我探讨,喜欢一起交流技术问题

关于我

个人博客:MartinHan的小站

博客网站:hanhan12312的专栏

知乎:MartinHan01