ThreadLocal原理解析

什么是Threadlocal

ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同。ThreadLocal相当于提供了一种线程隔离,将变量与线程绑定。

ThreadLocal API

set:设置当前线程绑定的局部变量。
get:获取当前线程绑定的局部变量。
remove:移除当前线程绑定的变量。

ThreadLocal的基本用法

先上一段demo

public class Demo {

    public static void main(String[] args) {
        int a = 10;
        ThreadLocal<Integer> threadLocal = new ThreadLocal();
        
        new Thread(() -> {
            threadLocal.set(a + 20);
            System.out.println(Thread.currentThread().getName() + threadLocal.get());
            threadLocal.remove();
            System.out.println(Thread.currentThread().getName() + threadLocal.get());
        }).start();
        
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + threadLocal.get());
        }).start();
    }
}

我们之前就知道,ThreadLocal是为我们提供一个线程局部变量的,那我们测试的方法就是创建两个线程,使用ThreadLocal去存取值,看看两个线程之间会不会互相影响,上面的这段代码我们来简单分析一下。

ThreadLocal和普通变量一样,也是通过new的方式来创建实例。

ThreadLocal<Integer> threadLocal = new ThreadLocal();

创建出ThreadLocal实例之后,我们如何来个它赋值呢,于是就有了

threadLocal.set(a + 20);

这样我们就给ThreadLocal赋值了,赋值之后怎么拿到这个值呢?

threadLocal.get()

至此,我们就演示了完整的ThreadLocal使用方法了。

ThreadLocal<Integer> threadLocal = new ThreadLocal();
threadLocal.set(a + 20);
threadLocal.get()

上述代码中还有一段remove

threadLocal.remove();

从字面意思理解,这就是删除操作,具体什么意思,看后面分析就明白了。

那么上述demo的输出结果是什么呢,在看输出结果之前,再次强调一点,ThreadLocal可以提供线程内的局部变量,各个线程之间互不干扰。

在第一个线程中:

new Thread(() -> {
    // 赋值
    threadLocal.set(a + 20);
    // 获取值并打印
    System.out.println(Thread.currentThread().getName() + threadLocal.get());
    threadLocal.remove();
    // remove后再次获取结果打印
    System.out.println(Thread.currentThread().getName() + threadLocal.get());
}).start();

第二个线程中:

new Thread(() -> {
    // 获取值并打印
    System.out.println(Thread.currentThread().getName() + threadLocal.get());
}).start();

按普通对象的逻辑考虑,两个线程中打印出的结果应该是一致的,因为他们使用的都是同一个ThreadLocal对象,但通过控制台输出,我们可以看到:

从控制台的结果看来,我们在一个线程中给ThreadLocal赋值,在另一个线程中好像并不能获取到,这印证了我们在开篇所说的ThreadLocal可以提供线程内部的局部变量。

我们给上述demo做一点小改动

public class Demo {
    public static void main(String[] args) {
        int a = 10;
        ThreadLocal<Integer> threadLocal = new ThreadLocal();
        
        new Thread(() -> {
            threadLocal.set(a + 20);
            System.out.println(Thread.currentThread().getName() + threadLocal.get());
            threadLocal.remove();
            System.out.println(Thread.currentThread().getName() + threadLocal.get());
        }).start();

        new Thread(() -> {
            threadLocal.set(a + 10);
            System.out.println(Thread.currentThread().getName() + threadLocal.get());
        }).start();
    }
}

在第二个线程中,也对ThreadLocal进行一次赋值。

似乎明白了,对于ThreadLocal而言,每个线程都是有一个单独存在的,相当于一个副本,线程之间互不影响,这里面还有一个null是因为调用了:

threadLocal.remove();

这相当于把值删除了,自然为空,想一想,上述的结果不就说明了ThreadLocal的作用吗?提供线程局部变量,每个线程都有自己的一份,线程之间没有影响。

也许有人会有疑问,为什么明明调用的是同一个ThreadLocal对象的get方法,获取到的结果却大不一样。这就需要往ThreadLocal的底层源码去看看了。

ThreadLocal底层原理

get源码解读

首先来看看get源码的真面目。

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程中独立的ThreadLocalMap,所以ThreadLocal是线程隔离的
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

这个就是get方法的实现,接下来我们就来仔细分析,为什么在另一个线程中调用get方法获取到的值是null,也就是这个。

new Thread(() -> {
    // 获取值并打印
    System.out.println(Thread.currentThread().getName() + threadLocal.get());
}).start();

首先来看get方法的前两句

 // 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程中独立的ThreadLocalMap,所以ThreadLocal是线程隔离的
ThreadLocalMap map = getMap(t);

先获取到当前线程,然后根据当前线程得到一个ThreadLocalMap对象,这个ThreadLocalMap是啥暂且不论,得到ThreadLocalMap后进行如下判断:

if (map != null) {
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
        @SuppressWarnings("unchecked")
        T result = (T)e.value;
        return result;
    }
}

也就是判断ThreadLocalMap是否为空,在当前线程中,我们并没有对ThreadLocalMap进行过实例化,所以这里当然为空,所以代码并不会进入到if语句块里,而是直接往下走下面代码,注意看代码中的注释说明。

return setInitialValue();

private T setInitialValue() {
    // initialValue方法直接返回null
    T value = initialValue();
    Thread t = Thread.currentThread();
    // 根据当前线程获取ThreadLocalMap,此处为null
    ThreadLocalMap map = getMap(t);
    // 这里的ThreadLocalMap当然为空,所以走else
    if (map != null)
        map.set(this, value);
    else
        // 创建ThreadLocalMap对象
        createMap(t, value);
    // 返回value,也就是initialValue方法的返回值null
    return value;
}

protected T initialValue() {
    return null;
}

/**
 * 创建ThreadLocalMap对象,将其与当前线程绑定
 * @param t 当前线程
 * @param firstValue firstValue此处为null
 */
void createMap(Thread t, T firstValue) {
    // this代表ThreadLocal对象,此时firstValue是null
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

注意在创建ThreadLocalMap对象时的this是指ThreadLocal对象。

ThreadLocal<Integer> threadLocal = new ThreadLocal();

通过上述分解,可以清楚的得出为什么在另一个线程中调用get方法得到的值是null。

接下来我们来看一下在new出ThreadLocalMap的时候都做了些什么。

/**
 * ThreadLocalMap构造方法
 * @param firstKey ThreadLocal对象
 * @param firstValue ThreadLocal对象中存储的值
 */
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    // 给Entry数组初始化,初始化容量为INITIAL_CAPACITY(16)
    table = new Entry[INITIAL_CAPACITY];
    // 计算数组下标
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // 创建一个Entry对象,存储到数组中,注意从get方法中传递过来的firstValue为null
    table[i] = new Entry(firstKey, firstValue);
    // 设置数组中有几条数据
    size = 1;
    // 根据初始容量设置增长因子
    setThreshold(INITIAL_CAPACITY);
}

从构造方法中得知,在创建ThreadLocalMap的时候会初始化一个Entry数组,并会创建一个Entry对象存入数组中,至于Entry对象是什么,接着往后看自然就明白了。

在了解构造方法中做了什么后,我们回到之前的get方法中的if判断,我们知道在没有调用set方法的时候直接调用get方法,此时map是为空的,但是在第一次调用get方法后,就会创建一个ThreadLocalMap对象。所以如果我们进行二次调用get方法,此处的map就应该不是null了,应该会走到if语句块中。

if (map != null) {
    // 获取Entry对象
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
        @SuppressWarnings("unchecked")
        T result = (T)e.value;
        return result;
    }
}

首先来看if语句块中的第一句代码

ThreadLocalMap.Entry e = map.getEntry(this);

通过ThreadLocal对象获取到Entry对象,前面讲到过在创建ThreadLocalMap对象的时候会初始化一个Entry数组,并且会创建一个Entry对象存储到数组中,所以此时根据ThreadLocal对象获取到的Entry对象一定不是null。所以代码会进入到下一个if判断。

if (e != null) {
    // 在前面分析的构造方法中我们有提到,创建的Entry对象中的value为null
    @SuppressWarnings("unchecked")
    T result = (T)e.value;
    return result;
}

在前面分析的构造方法中我们有提到,创建的Entry对象中的value为null,所以此处返回的值依然为null,也就是说如果没有调用set方法进行赋值,第二次调用get方法的时候ThreadLocalMap对象并不为null,但是get方法最终返回的值依然为null。

set源码解读

根据前面对get方法的分析,我们可以猜测set方法中应该也有一个对ThreadLocalMap是否为空的判断,并且如果为空的话会创建一个ThreadLocalMap对象,然后将需要set的value放到ThreadLocalMap中去。

public void set(T value) {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程中独立的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // 赋值:key为当前threadLocal实例,map中可以存储多个threadLocal对象
        map.set(this, value);
    else
        // 创建当前线程的ThreadLocalMap
        createMap(t, value);
}

set方法的实现逻辑跟get方法类似,也是先获取到当前线程,然后根据当前线程得到ThreadLocalMap,我们在第一个线程中第一次调用set方法的时候,此时ThreadLocalMap为null,所以会走createMap(t, value)创建ThreadLocalMap。

new Thread(() -> {
    threadLocal.set(a + 20);
    System.out.println(Thread.currentThread().getName() + threadLocal.get());
    threadLocal.remove();
    System.out.println(Thread.currentThread().getName() + threadLocal.get());
}).start();

与get方法中的createMap(t, value)不同的是这里的value就不再是null了,而是set的参数a+20。接着来看createMap方法的实现。

/**
 * 创建ThreadLocalMap对象,将其与当前线程绑定
 * @param t 当前线程
 * @param firstValue firstValue此处为set方法中传递过来的参数
 */
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

熟悉的配方,与get方法中一模一样。

当然如果是在ThreadLocalMap存在的情况下进行set操作,那么就会直接走map.set(this, value)方法。

再来看一下这个线程中的get方法

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

由于之前set的时候已经创建了ThreadLocalMap,所以这里不会再继续创建ThreadLocalMap,会直接走

ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
    @SuppressWarnings("unchecked")
    T result = (T)e.value;
    return result;
}

ThreadLocalMap的源码解读

先来看一张图,借助这张图便于我们理解

经过我们上面的分析,我们知道ThreadLocalMap设置值的方式是key-value的形式,也知道了这里的key其实就是ThreadLocal的实例,value就是要设置的值。

这里我们看下ThreadLocalMap,它其实是一个数据结构,就是用来存放我们的值的,而且它也是ThreadLocal的一个核心,我们通过上面这张图,首先要知道的一点就是:

ThreadLocalMap中存储的是Entry对象,Entry对象中存放的是key和value。

在前面提到过很多次的Entry对象究竟是什么呢,我们接下来来揭开他的神秘面纱。

在ThreadLocalMap中其实是维护了一张哈希表,这个表里面就是存放Entry对象,而每一个Entry对象简单来说就是存放了我们的key和value值。

那么这个是如何实现的呢?首先我们来想,Entry对象是存放在ThreadLocalMap中,那么对于TreadLocalMap而言就需要一个什么来存放这个Entry对象,我们可以想成一个容器,也就是说ThreadLocalMap需要有一个容器来存放Entry对象,我们来看ThreadLocalMap的源码实现:

/**
 * The table, resized as necessary.
 * table.length MUST always be a power of two.
 */
private Entry[] table;

在ThreadLocalMap中定义了一个Entry数组table,这个就是存放Entry对象的容器,也即我们之前提到的ThreadLocalMap中维护的哈希表。

提到哈希表就涉及到一个哈希表的扩容问题,哈希表的扩容就必然关系到增长因子,那么什么叫增长因子呢?

简单的说就是一个值,当哈希表里面的数据量达到容量的某个百分比的时候,就会触发哈希表的扩容,这个百分比就是增长因子,ThreadLocalMap中的增长因子如下

/**
 * The next size value at which to resize.
 */
private int threshold; // Default to 0

/**
 * Set the resize threshold to maintain at worst a 2/3 load factor.
 */
private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

ThreadLocalMap中定义了一个属性threshold,当哈希表中的数据量超过这个值的时候,哈希表就需要进行扩容。

既然这个哈希表是用来存储Entry对象的,那么Entry对象到底是如何存储到哈希表中去的呢。

private void set(ThreadLocal<?> key, Object value) {
    
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

我们来一行行分析这段代码,首先来看前两行

Entry[] tab = table;
int len = tab.length;

tab就是前面所说的哈希表,这个哈希表在ThreadLocalMap的构造方法中会进行实例化,初始容量为16(这个我们前面有进行分析)。len是这个哈希表的容量大小。

那么Entry对象该放到哈希表的哪个位置呢,我们接着往下看

int i = key.threadLocalHashCode & (len-1);

这一步的目的是为了获取一个下标,哈希表的存取数据是依靠一个下标去实现的,所以会根据这个下标来决定往哪一个位置去存取数据。

threadLocalHashCode是ThreadLocal中的一个变量,可以理解为ThreadLocal的hashcode,当然实际并不等于hashcode。

/**
 * ThreadLocals rely on per-thread linear-probe hash maps attached
 * to each thread (Thread.threadLocals and
 * inheritableThreadLocals).  The ThreadLocal objects act as keys,
 * searched via threadLocalHashCode.  This is a custom hash code
 * (useful only within ThreadLocalMaps) that eliminates collisions
 * in the common case where consecutively constructed ThreadLocals
 * are used by the same threads, while remaining well-behaved in
 * less common cases.
 */
private final int threadLocalHashCode = nextHashCode();

/**
 * The next hash code to be given out. Updated atomically. Starts at
 * zero.
 */
private static AtomicInteger nextHashCode =
    new AtomicInteger();

/**
 * The difference between successively generated hash codes - turns
 * implicit sequential thread-local IDs into near-optimally spread
 * multiplicative hash values for power-of-two-sized tables.
 */
private static final int HASH_INCREMENT = 0x61c88647;

/**
 * Returns the next hash code.
 */
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

获取到下标值后,接着往下走

for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
    ThreadLocal<?> k = e.get();

    if (k == key) {
        e.value = value;
        return;
    }

    if (k == null) {
        replaceStaleEntry(key, value, i);
        return;
    }
}

首先要知道这是一个for循环,根据一个下标值得到一个新的Entry对象,然后进入循环条件 也即是这个Entry对象不为null,然后执行循环体,循环体中有两个判断,还有一个根据当前Entry对象得到ThreadLocal的引用,也即是Key,不过这里定义为k。

现在我们要知道,我们是要往Entry数组中放入一个新的Entry对象,具体放到哪里由i这个下标值确定,具体的位置就是table[i],所以会出现的情况就有这个位置原本就有一个Entry对象或者为null,于是如果原本就有的话而且引用的是同一个ThreadLocal的话,那么就把值给覆盖掉:

if (k == key) {
    e.value = value;
    return;
}

如果是这个位置的Entry对象的引用是null的话,我们就替换该Entry对象的引用和value值。
Entry本质上是个ThreadLocal的弱引用,所以它随时都有可能被回收掉,这样就会出现key值为null的Entry对象

if (k == null) {
    replaceStaleEntry(key, value, i);
    return;
}

当然,也会出现的情况就是这个位置不为null,而且也不是同一个ThreadLocal的引用(类似于hash冲突),那么就需要继续往后挪一个位置来放入新的数据:

e = tab[i = nextIndex(i, len)])

如果往后挪一个位置发现该位置不存在Entry对象,则直接结束循环走后面代码

tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
    rehash();

这个就是创建一个新的Entry对象放入到哈希表中,因为Entry数组多了一个Entry对象,所以总条目需要加一,而这个if判断则是为了看看当前存储的对象个数是否达到了增长因子,也就是判断下是否需要扩容,如果需要扩容了该怎么办呢?这个时候要依靠的就是这个rehash函数了。

如果达到了增长因子,那就需要重新扩充,而且还需要将所有的对象重新计算位置,我们来看rehash函数的实现:

private void rehash() {
    // 这一段代码的作用是清除哈希表中ThreadLocal为null的Entry对象
    expungeStaleEntries();

    // Use lower threshold for doubling to avoid hysteresis
    if (size >= threshold - threshold / 4)
        resize();
}

我们看到在if判断中判断的指标是增长因子的3/4,这是怎么回事,之前不是说增长因子是2/3嘛?超过这个值才需要扩容,这怎么变成了增长因子的3/4才开始扩容呢?我们之前说过,ThreadLocalMap中存储的是Entry对象,Entry本质上是个ThreadLocal的弱引用,所以它随时都有可能被回收掉,这样就会出现key值为null的Entry对象,这些都是用不到的,需要删除掉来腾出空间,这样一来,实际上存储的对象个数就减少了,所以后面的判断就是增长因子的3/4,而不是增长因子2/3了。

那该如何获取到Entry对象中的数据呢?也即是我们使用ThreadLocal的实例去调用get方法取值:

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

这段代码之前分析过,这里主要来看getEntry方法的实现

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

getEntry的方法逻辑和set方法类似,都是先确定一个下标,然后获取对应下标的Entry对象。如果该位置的Entry对象不为null并且该Entry对象引用的ThreadLocal对象就是当前ThreadLocal对象,那么直接返回该Entry对象。

 if (e != null && e.get() == key)
    return e;

如果该位置不存在Entry对象,或者该Entry对象引用的ThreadLocal为null,则走else代码

else
    return getEntryAfterMiss(key, i, e);

这个方法无非就是当当前Entry对象不满足条件时,下标往后挪一个位置继续获取Entry对象进行判断。

ThreadLocal的内存泄露

先介绍两个概念,内存泄漏和内存溢出。

内存泄漏
说的简单点那就是因为操作不当或者一些错误导致没有能释放掉已经不再使用的内存,这就是内存泄漏,也就是说,有些内存已经不会再使用了,但是却没有给它释放掉,这就一直占用着内存空间,从而导致了内存泄漏。

内存溢出
这个简单点说就是内存不够用了,我运行一个程序比如说需要50M的内存,但是现在内存就剩下20M了,那程序运行就会发生内存溢出,也就是告诉你内存不够用,这时候程序就无法运行了。

了解了这两个基本概念之后,我们再来了解ThreadLocal的内存泄漏问题,回顾一下之前这张图:

经过我们上述的讨论,我们大致知道了ThreadLocal其实本质上是在每个线程中单独维护了一个ThreadLocalMap数据结构,这个ThreadLocalMap是每个线程独有的,只有根据当前线程才能找到当前线程的这个ThreadLocalMap,这就实现了线程之前的隔离。
我们看上面那张图,每个线程根据找到自己维护的ThreadLocalMap,然后可以操作这个数据结构,往里面存取数据,而ThreadLocalMap中维护的就是一个Entry数组,每个Entry对象就是我们存放的数据,它是个key-value的形式,key就是ThreadLocal实例的弱引用,value就是我们要存放的数据,也就是一个ThreadLocal的实例会对应一个数据,形成一个键值对。
如果有两个线程,持有同一个ThreaLocal的实例,这样的情况也就是Entry对象持有的ThreadLocal的弱引用是一样的,但是两个线程的ThreadLocalMap是不同的,记住一点,那就是ThreadLocalMap是每个线程单独维护的。

那么为什么会出现内存泄漏呢????

那我们现在来看,为什么ThreadLocal会出现内存泄漏,我们之前也说过了,Entry对象持有的是键就是ThreadLocal实例的弱引用,弱引用有个什么特点呢?那就是在垃圾回收的时候会被回收掉,可以根据上图想一下,图中虚线就代表弱引用,如果这个ThreadLocal实例被回收掉,这个弱引用的链接也就断开了,就像这样:

那么这样在Entry对象中的key就变成了null,所以这个Entry对象就没有被引用,因为key变成看null,就取不到这个value值了,再加上如果这个当前线程迟迟没有结束,ThreadLocalMap的生命周期就跟线程一样,这样就会存在一个强引用链,所以这个时候,key为null的这个Entry就造成了内存泄漏。因为它没有用了,但是还没有被释放。

既然出现了内存泄漏,那我们如何解决呢????

明白了如何产生的内存泄漏,也就知道了怎么解决,经过上面的分析,我们大致知道了在ThreadLocalMap中存在key为null的Entry对象,从而导致内存泄漏,那么只要把这些Entry都给删除掉,也就解决了内存泄漏。

我们每次使用ThreadLocal就会随线程产生一个ThreadLocalMap,里面维护Entry对象,我们对Entry进行存取值,那么如果我们每次使用完ThreadLocal之后就把对应的Entry给删除掉,这样不就解决了内粗泄漏嘛,那怎么做呢?

在ThreadLocal中提供了一个remove方法:

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

private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}

这个就是根据key删除掉对应的Entry,如此一来,我们就解决了内存泄漏问题,因为可能出现内存泄漏的Entry,在我们使用完之后就立马删除了。

所以对于ThreadLocal而言,就应该像使用锁一样,加锁之后要记得解锁,也就是调用它的remove方法,用完就清理。

总结

至此,我们已经对ThreadLocal做了一个较为全面和深入的分析,大家应该也对它有了更深的印象,下面针对本文来做一个简单的总结:

1.ThreadLocal是用来提供线程局部变量的,在线程内可以随时随地的存取数据,而且线程之间是互不干扰的。

2.ThreadLocal实际上是在每个线程内部维护了一个ThreadLocalMap,这个ThreadLocalMap是每个线程独有的,里面存储的是Entry对象,Entry对象实际上是个ThreadLocal的实例的弱引用,同时还保存了value值,也就是说Entry存储的是键值对的形式的值,key就是ThreadLocal实例本身,value则是要存储的数据。

3.TreadLocal的核心是底层维护的ThreadLocalMap,它的底层是一个自定义的哈希表,增长因子是2/3,增长因子也可以叫做是一个阈值,底层定义为threshold,当哈希表容量大于或等于阈值的3/4的时候就开始扩容底层的哈希表数组table。

4.ThreaLocalMap中存储的核心元素是Entry,Entry是一个弱引用,所以在垃圾回收的时候,ThreadLocal如果没有外部的强引用,它会被回收掉,这样就会产生key为null的Entry了,这样也就产生了内存泄漏。

5.在ThreadLocal的get(),set()和remove()的时候都会清除ThreadLocalMap中key为null的Entry,如果我们不手动清除,就会造成内存泄漏,最佳做法是使用ThreadLocal就像使用锁一样,加锁之后要解锁,也就是用完就使用remove进行清理。

posted @ 2021-05-12 17:10  danger0us  阅读(84)  评论(0)    收藏  举报