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

前言

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

ThreadLocal

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

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

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

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

源码解释

set方法源码

scss 复制代码
// 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);
}
ini 复制代码
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方法源码

scss 复制代码
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 <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
        // SuppliedThreadLocal是ThreadLocal的子类,重写了initialValue方法,通过传入一个Supplier,指定初始化值
        return new SuppliedThreadLocal<>(supplier);
}
private Entry getEntry(ThreadLocal<?> 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;
}

三者关系

总结

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

强软弱虚四大引用

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

  1. 强引用(Strong Reference):这是最常见的引用类型。当我们使用 new 关键字创建对象时,默认就是使用强引用。如果一个对象具有强引用,即存在一个强引用变量引用它,那么垃圾回收器就不会回收该对象 。只有当对象没有任何强引用时,才会被认为是不再需要的,可以被垃圾回收
  2. 软引用(Soft Reference):软引用用于描述还有用但非必需的对象。在内存不足时,垃圾回收器可能会选择回收软引用对象 。使用软引用可以实现一些缓存功能,在内存不足时释放缓存中的对象,从而避免 OutOfMemoryError。可以使用 SoftReference 类来创建软引用。
  3. 弱引用(Weak Reference):弱引用的生命周期更短暂,只要垃圾回收器发现一个对象只有弱引用与之关联,就会立即回收该对象 。弱引用通常用于实现一些特定的缓存或关联数据结构,当对象的强引用被释放后,关联的弱引用对象也会被自动清除。可以使用 WeakReference 类来创建弱引用。
  4. 虚引用(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就会被收回。

相关推荐
颜淡慕潇6 分钟前
【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
后端·云原生·容器·kubernetes
向前看-7 小时前
验证码机制
前端·后端
超爱吃士力架9 小时前
邀请逻辑
java·linux·后端
AskHarries11 小时前
Spring Cloud OpenFeign快速入门demo
spring boot·后端
isolusion12 小时前
Springboot的创建方式
java·spring boot·后端
zjw_rp12 小时前
Spring-AOP
java·后端·spring·spring-aop
TodoCoder13 小时前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
凌虚14 小时前
Kubernetes APF(API 优先级和公平调度)简介
后端·程序员·kubernetes
机器之心14 小时前
图学习新突破:一个统一框架连接空域和频域
人工智能·后端
.生产的驴15 小时前
SpringBoot 对接第三方登录 手机号登录 手机号验证 微信小程序登录 结合Redis SaToken
java·spring boot·redis·后端·缓存·微信小程序·maven