大家好,我是三叔,很高兴这期又和大家见面了,一个奋斗在互联网的打工人。
笔者曾在一文理解线程安全中有提到ThreadLocal,笔者就在思考,ThreadLocal是如何知道拿到key呢,这篇博客笔者将会从底层源码介绍一下ThreadLocal是如何获取到key值的以及ThreadLocal是如何发生内存泄漏。
ThreadLocal是如何获取到key值的
每一个线程的Thread对象中都有一个ThreadLocalMap对象,这个对象存储了一组以threadLocalHashCode为键,以本地线程变量为值的k-v值对,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在线程k-v中找到对应的线程变量。
简单来说就是,ThreadLocal是通过线程对象中的ThreadLocalMap来存储和查找key-value对的。每个ThreadLocal实例都会有一个唯一的integer类型的key值,而value则可以是任意类型的对象。在Java中,线程对象中的ThreadLocalMap是通过ThreadLocal的弱引用来关联线程本地变量的。
ThreadLocal类内部维护了一个静态变量nextHashCode,用于生成每个ThreadLocal实例对应的唯一的integer类型的key值。每次创建一个新的ThreadLocal实例时,都会将nextHashCode的值作为该ThreadLocal实例的key值,并将nextHashCode的值自增。因此,每个ThreadLocal实例的key值都是唯一的,且按照创建顺序递增。
让我们从源码来看:
public class ThreadLocal<T> {
// ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值
private final int threadLocalHashCode = nextHashCode();
// 静态变量nextHashCode,用于生成每个ThreadLocal实例对应的唯一的integer类型的key值
private static AtomicInteger nextHashCode = new AtomicInteger();
// HASH_INCREMENT 是一个常量,是 0x61c88647,用于计算 hash 值
private static final int HASH_INCREMENT = 0x61c88647;
// 返回下一个哈希码
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
}
当我们使用ThreadLocal的set()方法设置一个变量时,ThreadLocal会首先获取当前线程对象的ThreadLocalMap,并将ThreadLocal实例作为key,将变量作为value,存储到该ThreadLocalMap中。
public void set(T value) {
Thread t = Thread.currentThread();
// 当前线程对象的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 将ThreadLocal实例作为key,将变量作为value
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
当我们使用ThreadLocal的get()方法获取一个变量时,ThreadLocal会首先获取当前线程对象的ThreadLocalMap,并将ThreadLocal实例作为key,从ThreadLocalMap中获取对应的value,然后返回该value。由于ThreadLocal是与当前线程对象相关联的,因此每个线程对象都有自己ThreadLocalMap,因此每个线程中的ThreadLocal变量都是相互独立的,互不影响。
public T get() {
Thread t = Thread.currentThread();
// 同set方法一样,先获取当前对象的map
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
// @SuppressWarnings是一个用于告诉编译器忽略指定类型的警告的注解
@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);
}
ThreadLocalMap中的key是弱引用
需要注意的是,通过源码了解到,ThreadLocalMap的Entry继承了WeakReference
ThreadLocal中使用的ThreadLocalMap类中的Entry对象会持有ThreadLocal实例的弱引用作为key,而弱引用在内存不足时会被垃圾回收器回收,如果这时ThreadLocal实例已经被回收,那么对应的Entry对象却不会被自动清除,从而导致内存泄漏。
因此,在使用ThreadLocal时,我们应该注意及时清理ThreadLocal实例,并在不需要的时候将其设置为null,以便垃圾回收器可以及时回收。
package com.yin.demo.service;
public class ThreadLocalExample {
// 创建一个threadLocal对象
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 设置ThreadLocal值
threadLocal.set(2023);
// 获取ThreadLocal值
int value = threadLocal.get();
System.out.println("ThreadLocal value: " + value);
// 清除ThreadLocal值
threadLocal.remove();
// 将ThreadLocal实例设置为null
threadLocal = null;
}
}
在上面的示例代码中,我们首先使用ThreadLocal的set()方法设置一个值,然后使用ThreadLocal的get()方法获取该值,并打印输出。接着,我们使用ThreadLocal的remove()方法清除该值,然后将ThreadLocal实例设置为null,以便垃圾回收器可以及时回收。