Java高并发编程之ThreadLocal

ThreadLocal 在java中是充当“线程本地变量”使用,通过set()和get()来对这个局部变量进行操作,在每个线程都会创建该对象从而实现线程之间的隔离,具有一定程度上的线程安全。ThreadLocal常见应用场景为数据库连接、Session管理等。

# 深入ThreadLocal类

ThreadLoca提供一下常用方法:

protected T initialValue(); // 用来在使用时进行重写的,是一个延迟加载方法
public T get();             // 获取ThreadLocal在当前线程中保存的变量副本
public void set(T value);   // 设置当前线程中变量的副本
public void remove()        // 移除当前线程中变量的副本
1
2
3
4

# get()方法实现

public T get() {
    Thread t = Thread.currentThread();                // 取得当前线程
    ThreadLocalMap map = getMap(t);                   // 获取当前线程对应的ThreadLocalMap
    if (map != null) {                                // Not Null
        ThreadLocalMap.Entry e = map.getEntry(this);  // 获取到<key,value>键值对,注意这里获取
                                                      // 键值对传进去的是this,而不是当前线程t。
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;                    
            return result;                            // 存在,则返回value值。
        }
    }
    return setInitialValue();                         // 否则调用setInitialValue方法返回value
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为KeysetInitialValue()实现:

private T setInitialValue() {
    T value = initialValue();                         // return null;
    Thread t = Thread.currentThread();                // 取得当前线程
    ThreadLocalMap map = getMap(t);                   // 获取当前线程对应的ThreadLocalMap 
    if (map != null)
        map.set(this, value);                         // 值为null,只有调用set()方法时才初始化value
    else
        createMap(t, value);                          
    return value;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);  // 创建当前线程对应的ThreadLocalMap
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# set()方法实现

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);       // 获取当前线程的成员变量map
    if (map != null)
        map.set(this, value);             // map非空,则重新将ThreadLocal和新的value副本放入到map中。
    else
        createMap(t, value);              // map空,则对线程的成员变量ThreadLocalMap进行初始化创建,
                                          // 并将ThreadLocal和value副本放入map中。
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# remove()方法实现

public void remove() {
 ThreadLocalMap m = getMap(Thread.currentThread());
 if (m != null)
     m.remove(this);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
1
2
3
4
5
6
7
8
9

小结:在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,key为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为key,以ThreadLocal要保存的副本变量为value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

在进行get之前,必须先set,否则会报空指针异常。如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

# 内存泄漏问题

由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

解决方案: 既然Key是弱引用,解决方案就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。

如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。

参考资料 Java并发编程:深入剖析ThreadLocal ThreadLocal-面试必问深度解析

上次更新: 2020/07/28, 14:07:00
最近更新
01
RabbitMQ简介
10-27
02
聊聊Java多态
10-21
03
JVM垃圾回收器
10-16
更多文章>