问题引出

今天学习《Java并发编程的艺术》一书第4章中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
class Person {
String name;

Person(String name) {
this.name = name;
}

public String toString() {
return name;
}
}

class ThreadLocalIs implements Runnable {
ThreadLocal<Person> threadLocal = new ThreadLocal<>();
Integer value;

public void set(Person value) {
threadLocal.set(value);
}

public void set(Integer value) {
this.value = value;
}

@Override
public void run() {
System.out.println(value);
System.out.println(threadLocal.get());
}
}

public static void main(String[] args) throws Exception {
ThreadLocalIs is = new ThreadLocalIs();
Person fan = new Person("Fan");
is.set(fan);
is.set(25);
Thread t = new Thread(is);
t.start();
}
/**
* Output:
* 25
* null
*/

可以看到,在输出中,对is引用的ThreadLocalIs对象中的Integer类型的value赋值被成功输出,对ThreadLocal类型的threadLocal变量的赋值却丢失了。折腾了很久,才搞明白是怎么回事。

原因

这是因为,ThreadLocal对象隶属于线程管理,而非对象管理。也就是说,虽然is对象中声明了一个ThreadLocal对象,但是对它的赋值操作却是在线程中执行的,也即main线程中进行的赋值,那么,main线程中中的ThreadLocalMap类型的threadLocals中就会挂载is对象中的threadLocal对象。也即,该ThreadLocal对象属于main线程,而不属于is对象。如果将set()操作放在ThreadLocalIs类中的run()方法内执行,那么该ThreadLocal对象就会挂载在新建它的线程上,也即t上。

ThreadLocal

关于ThreadLocal的原理源码分析啥的这里就不再赘述,网上有很多优质的博文做了详细的介绍说明。这里给出一些自己的理解总结:

  • ThreadLocal,线程变量,是一个以ThreadLocal对象为键、任意对象为值的存储结构,例如:ThreadLocal<Person> obj = new ThreadLocal<>(); Person fan = new Person(); obj.set(fan);代码中,对于该线程中,obj为键,fan对象为值。线程可以根据obj对象(ThreadLocal对象)查找到fan对象(值)。
  • 每个线程维护一个ThreadLocalMap,该Map中存放着所有在该线程中注册的ThreadLocal键值对对象。因此,ThreadLocal是线程隔离的,也即线程安全的。