ThreadLocal的实现原理,ThreadLocal为什么使用弱引用

2023年 8月 7日 30.4k 0

前言

本文将讲述ThreadLocal的实现原理,还有## ThreadLocal为什么使用弱引用。

ThreadLocal

ThreadLocal 是 Java 中的一个类,用于在多线程环境下为每个线程提供独立的变量副本。它通常用于解决多线程并发访问共享变量时的线程安全性问题。

ThreadLocal 的工作原理是每个线程内部维护一个 ThreadLocalMap 对象,该对象用于存储每个线程的变量副本。当通过 ThreadLocal 对象获取变量时,它会首先检查当前线程是否已经创建了该变量的副本,如果有,则直接返回副本;如果没有,则通过初始化方法创建一个新的副本,并将其保存在当前线程的 ThreadLocalMap 中。

使用 ThreadLocal 时,每个线程都可以独立地访问和修改自己的变量副本,而不会影响其他线程的副本。这使得在多线程环境中共享变量变得更加安全和可靠。

需要注意的是,使用 ThreadLocal 时要注意及时清理不再使用的变量副本,以避免内存泄漏问题。可以通过调用 remove() 方法来清除当前线程的变量副本。

源码解释

set方法源码

// ThreadLocal的set方法,value是要保存的值
public void set(T value) {
    // 得到当前线程对象
    Thread t = Thread.currentThread();
    // 得到当前线程对象关联的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    // 得到map对象就保存值,键为当前ThreadLocal对象
    // 如果没有map对象就创建一个map对象,保存值
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
// 得到当前线程关联的ThreadLocalMap对象
ThreadLocalMap getMap(Thread t) {
       return t.threadLocals;
}
// 创建一个ThreadLocalMap对象,赋给当前线程的threadLocals属性,并且存入值
void createMap(Thread t, T firstValue) {
       t.threadLocals = new ThreadLocalMap(this, firstValue);
}
private void set(ThreadLocal key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    // 通过key计算在tab数组中的槽位i
    int i = key.threadLocalHashCode & (len-1);
    // 拿到槽位上的Entry对象,如果不为null,则进入循环,如果为null则表示可以直接加入该槽位
    // e = tab[i = nextIndex(i, len)])取出下一个槽位的Entry实体,如果为null,则表示可以直接添加进该槽位
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        // 拿到与当前Entry有关联的ThreadLocal对象
        ThreadLocal k = e.get();
        // 如果k与当前要保存值的key相等,则替换掉value,相当于修改key的值
        if (k == key) {
            e.value = value;
            return;
        }
        // 检查当前节点的ThreadLocal如果为null,表示ThreadLocal已经被gc回收,则调用 replaceStaleEntry() 方法来替换陈旧的 Entry,将新的 ThreadLocal 和值插入到数组中的索引位置 i 处,并返回。
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // 创建一个Entry对象,加入i槽位
    tab[i] = new Entry(key, value);
    // 记录Entry对象个数
    int sz = ++size;
    // cleanSomeSlots清理陈旧的Entry,清理完后如果大于阈值,则调用rehash扩容数组
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

get方法源码

public T get() {
    // 获取当前线程对象
    Thread t = Thread.currentThread();
    // 得到当前线程关联的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 通过key获取到Entry对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        // Entry不为空,则直接获取值返回结果
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果map为null,或者Entry为null,则返回一个初始化值
    return setInitialValue();
}
private T setInitialValue() {
        // 如果是在调用构造器初始化的ThreadLocal对象,该方法直接返回null
        // 如果是调用的静态方法withInitial,则返回你指定的一个初始化则
        // 并且还会把该初始化的值保存进ThreadLocalMap
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
}
public static  ThreadLocal withInitial(Supplier key) {
    // 计算当前key的落脚点
    int i = key.threadLocalHashCode & (table.length - 1);
    // 取出落脚点的Entry对象
    Entry e = table[i];
    // 如果e不为空,并且跟e关联的ThreadLocal对象等于当前的key,则返回当前e对象
    if (e != null && e.get() == key)
        return e;
    // 否则进入getEntryAfterMiss
    else
        return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    // 如果e为null,则直接返回null,表示当前key并没有数据
    while (e != null) {
        // 取出与e关联的ThreadLocal对象
        ThreadLocal k = e.get();
        // 判断k是否等于当前的ThreadLocal对象
        if (k == key)
            return e;
        // 当前k是否等于null,为null表示被gc垃圾回收,就清理旧的Entry对象
        if (k == null)
            expungeStaleEntry(i);
        else
            // 否则k不为null,取出下一个槽位,接着循环
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

三者关系

ThreadLocal三者关系.PNG

总结

可以看出实际保存线程局部变量的是ThreadLocalMap对象,每个线程都有一个这样的对象,保存的是键值对,键为当前的ThreadLocal对象,ThreadLocal对象一般设置为静态,非静态只会造成对象的冗余,因为ThreadLocalMap的键只能是当前ThreadLocal对象,所以只能保存一个键值对,如果要保存多个键值对,可以定义多个ThreadLocal对象作为不同的键,这样获取到的还是与线程有关联的ThreadLocalMap对象,而ThreadLocalMap的键是当前的ThreadLocal对象,多少个该对象,那就可以保存多少个值。

强软弱虚四大引用

在Java中,引用是用于引用对象的一个机制,它允许我们通过引用变量来操作和访问对象。在Java中,主要有以下几种引用类型:

  • 强引用(Strong Reference):这是最常见的引用类型。当我们使用 new 关键字创建对象时,默认就是使用强引用。如果一个对象具有强引用,即存在一个强引用变量引用它,那么垃圾回收器就不会回收该对象。只有当对象没有任何强引用时,才会被认为是不再需要的,可以被垃圾回收。
  • 软引用(Soft Reference):软引用用于描述还有用但非必需的对象。在内存不足时,垃圾回收器可能会选择回收软引用对象。使用软引用可以实现一些缓存功能,在内存不足时释放缓存中的对象,从而避免 OutOfMemoryError。可以使用 SoftReference 类来创建软引用。
  • 弱引用(Weak Reference):弱引用的生命周期更短暂,只要垃圾回收器发现一个对象只有弱引用与之关联,就会立即回收该对象。弱引用通常用于实现一些特定的缓存或关联数据结构,当对象的强引用被释放后,关联的弱引用对象也会被自动清除。可以使用 WeakReference 类来创建弱引用。
  • 虚引用(Phantom Reference):虚引用是最弱的引用类型,几乎没有实际的使用场景。虚引用的主要作用是跟踪对象被垃圾回收的状态。当垃圾回收器决定回收一个对象时,如果该对象有虚引用,将会在对象被回收之前,将虚引用加入到与之关联的引用队列中,供应用程序获取对象回收的状态信息。
  • 在内存管理方面,软引用和弱引用都可以用于解决一些特定的内存问题,例如缓存管理或对象关联。它们对于临时性或可替代性对象的管理非常有用,可以在内存紧张时进行垃圾回收,从而提高系统的性能和可用性。然而,需要注意的是,对于软引用和弱引用对象,程序应该在使用时进行必要的判空和恢复处理,以避免 NullPointerException 和其他相关问题。

    ThreadLocal为什么使用弱引用

    ThreadLocal 使用弱引用的主要原因是为了避免内存泄漏问题。

    当使用强引用持有 ThreadLocal 对象时,只有线程销毁或显式地调用 remove() 方法时,Entry 才会被释放。这可能导致在多线程环境下使用线程池时,即使线程已经使用结束处于空闲状态,对应的 Entry 仍然会存在于 ThreadLocalMap 中,导致无法回收相关资源,从而造成内存泄漏。

    使用弱引用作为 ThreadLocal 的键(key),可以解决这个问题。弱引用在垃圾回收时只要发现只有弱引用指向,则会被直接回收。因此,当线程结束且对应的 ThreadLocal 对象只有弱引用存在时,垃圾回收器会自动清理该弱引用,进而清理 ThreadLocalMap 中对应的 Entry。这样可以避免内存泄漏问题。

    需要注意的是,尽管 ThreadLocalMap 使用了弱引用来避免内存泄漏问题,但仍然需要在使用 ThreadLocal 后调用 remove() 方法,以确保及时清理 ThreadLocal 对象和对应的值。这是因为弱引用的回收时机不确定,不能完全依赖垃圾回收器的工作。

    当我们应该请求进来分配一个线程处理请求,此时ThreadLocal对象就会被创建,并且是一个强引用,当第一次把值存入时ThreadLocal时,就会通过Thread拿到或者创建一个ThreadLocalMap对象,并且存入我们的数据,此时ThreadLocal作为键就会被放入弱引用中,此时就算发送垃圾回收也不会回收ThreadLocal因为有一个强引用指向,但是一旦我们的请求执行完毕返回,线程处于空闲状态时,这个强引用就没了,此时就剩下一个弱引用,这个时候发生垃圾回收就ThreadLocal就会被收回。

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论