ThreadLocal

声明:本文源码基于JDK 1.8

定义:

ThreadLocal:线程局部变量。
一个成员变量,正常在并发的情况下是线程不安全的。一种解决方案是每个线程内部维护一个此变量的副本,使此变量在多线程之间实现隔离,保证线程安全。

作用:

  • 最常见数据库连接。
    操作数据库使用连接池,连接池的链接使用ThreadLocal来管理,能够实现当前线程的操作都是使用同一个Connection.
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
private static final ThreadLocal<Connection> THREAD_LOCAL = new ThreadLocal<>();

/**
* 取得数据库的连接对象,如果没有则自动创建新的连接。
* @return Connection
*/
public static Connection getConnection() {
Connection conn = THREAD_LOCAL.get();
if (conn == null) {
try {
Class.forName(DRIVER);
conn = DriverManager.getConnection(URL, USER, PASSWORD);
THREAD_LOCAL.set(conn);
} catch (Exception e) {
//
}
}
return conn;
}

/**
* 关闭数据库连接
*/
public static void close() {
Connection conn = THREAD_LOCAL.get() ;
if (conn != null) {
try {
conn.close();
THREAD_LOCAL.remove();
} catch (SQLException e) {
//
}
}
}
  • 维护session或token。
    Http本身是无状态的,前端发来的请求中一般包含用户的标识信息(类session),将信息使用ThreadLocal放入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
private static final int CAPACITY = 8;
private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL = new ThreadLocal<>();

/**
* setToken
* @param key key
* @param value value
*/
public static void set(String key, Object value) {
Map<String, Object> map = THREAD_LOCAL.get();
if (map == null) {
map = new HashMap<>(CAPACITY);
THREAD_LOCAL.set(map);
}
map.put(key, value);
}

/**
* getToken
* @param key key
* @return value
*/
public static Object get(String key) {
Map<String, Object> map = THREAD_LOCAL.get();
if (map == null) {
map = new HashMap<>(CAPACITY);
THREAD_LOCAL.set(map);
}
return map.get(key);
}

public static void remove() {
THREAD_LOCAL.remove();
}

原理:

ThreadLocal类的源码特别简单,加注释总共才700多行。

method

我们主要关注set() 和get()方法,看内部具体做了什么操作。

set

getMap

set()方法也比较简单,主要做了以下事情:

1:获取当前线程。

2:获取当前线程Thread类中的属性threadLocals(ThreadLocalMap)。

3:将value放入ThreadLocalMap中,而key就是ThreadLocal本身。如果ThreadLocalMap为null,则初始化。

这里比较绕的一点在于ThreadLocalMap的处理。

通过第一张图看到ThreadLocalMap是ThreadLocal的一个静态内部类,其实就是一个容器类Map,而其又是Thread类的一个属性。
总体流程就是:先找到当前线程Thread类,随后将value放入线程私有内存空间中的ThreadLocalMap中,key为ThreadLocal本身。

get

理解了ThreadLocal和Thread以及ThreadLocalMap的关系之后,get()方法看起来就更简单了:

1:获取当前线程类Thread。

2:获取当前线程私有内存空间的ThreadLocalMap。

3:通过key(ThreadLocal本身)使用map的getEntry()方法获取到value。

QA:

  • 经常看到说ThreadLocal使用不当会引起内存泄漏,具体是指什么?
    ThreadLocal在ThreadLocalMap中是以一个弱引用身份被Entry中的key引用的,因此如果ThreadLocal没有外部强引用来引用它,那么ThreadLocal会在下次JVM垃圾收集时被回收,这个时候就会出现Entry中key已经被回收,出现一个null key的情况,外部读取ThreadLocalMap中的元素时无法通过null key来找到value的。因此如果当前线程的生命周期过长,一直存在,那么其内部的ThreadLocalMap对象也会一直生存下来,这些null key 就存在一条强引用链的关系:Thread——>ThreadLoaclMap——>Entry——>Value.这条强引用链会导致Entry不会被回收,value也不会回收,但Entry中的key却已经被回收的情况造成内存泄露。但JVM团队已经考虑到这种情况,并做了一些措施来保证ThreadLocal尽量不会内存泄露:在ThreadLocal的get()、set()、remove()方法调用时会清除掉线程ThreadLocalMap中所有Entry中key为null的value,并将整个entry设置为null,利于下次回收。
  • 既然ThreadLocalMap的作用是每个线程一个,那么为什么不直接定义到Thread类中呢?反而要定义在ThreadLocal类中,然后还得交叉引用,这样可读性特别差,理解起来也不容易。那么为什么还要放在ThreadLocal中呢?
    将ThreadLocalMap定义在Thread类中看起来更符合逻辑,但是ThreadLocalMap并不需要Thread对象来操作,所以定义在Thread类中只会增加一些不必要的开销。定义在ThreadLocal中的原因是ThreadLocal类负责ThreadLocalMap的创建,仅当线程中设置第一个ThreadLocal时,才为当前线程创建ThreadLocalMap,之后所有的ThreadLocal变量将使用同一个ThreadLocalMap。总的来说就是:ThreadLocalMap不是必需品,定义在Thread中增加了成本,定义在ThreadLocal中按需创建。

总结:

从本质上讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set进去的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全。