ThreadLocal深入理解

大致理解

顾名思义,ThreadLocal指的就是Thread的本地文件,即每一个线程专属的本地变量。

详细定义如下:

ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

因此,当一个实例需要在多个线程中使用,且每个线程都希望该实例不会被其他线程说使用,这种情况下把实例放入ThreadLocal类中将会十分方便。常用场景有数据库链接和Session管理等。

深入分析

说完了基本意义和使用场景后,接下来需要分析ThreadLocal类的内部构造,明白其是如何实现的。

观察ThreadLocal类的内部结构后发现其内部包含了ThreadLocalMap的内部类,该map中的key为ThreadLocalMap的弱引用,value储存了所需的实例副本,其中ThreadLocalMap由每个线程自行维护。

static class Entry extends WeakReference<ThreadLocal> {
    Object value;

    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

初始化实例

当线程中ThreadLocalMap为空时,线程便会初始化map,如果存在map就会将实例放入map中。

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

其中ThreadLocalMap是每个线程单独维护的,都是取自于Thread类中的threadLocals变量。

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

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

需要注意的是,如果没有重写initialValue方法,实例默认为null

protected T initialValue() {
    return null;
}

获取实例

get方法首先获取了当前线程对象,然后通过getMap方法获取了当前线程中的ThreadLocalMap,接着以ThreadLocal为key寻找到对应的实例,最后判断实例是否为空,如果不为空直接返回,如果为空则初始化后再返回。

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

设置实例

设置实例与获取实例相似,也是获取当前线程中的ThreadLocalMap,然后以ThreadLocal为key设置其value

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

内存泄漏

分析完了ThreadLocal相关源码后,将其内存模型构建出来。

从内从模型可以看到ThreadLocalMap使用ThreadLocal的弱引用作为key,当ThreadLocal没有外部强引用时这会被GC回收,不过Entry中的 value因为被 ThreadLocalMap所引用,因此就会出现keynull的Entry。这种情况下将导致value无法引用并不能回收,造成内存泄漏。其实,ThreadLocalMap设计时已经考虑到这种情况,每当调用ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有keynullvalue

不过错误的代码编写难免会导致内存泄漏的发生,常见的有:

  • 使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏(参考ThreadLocal 内存泄露的实例分析)。
  • 分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏。

因此,每次使用完ThreadLocal,都需要调用其remove()方法清除数据,避免内存泄漏。

参考文献

深入分析 ThreadLocal 内存泄漏问题
Java并发编程:深入剖析ThreadLocal
Java进阶(七)正确理解Thread Local的原理与适用场景

原文地址:https://www.cnblogs.com/gforce/p/9323694.html