建议收藏!面试官最爱问的理解布隆过滤器算法的实现原理,一次性给你讲明白!

本文深入探讨了Java中的ThreadLocal,解释了为什么使用它来解决并发问题,以及如何实现线程封闭。文章详细介绍了ThreadLocal的工作原理,包括其与Thread的关系、内存管理和线程安全问题。同时,提到了ThreadLocalMap的实现细节,如弱引用、内存泄漏风险及清理策略,并建议在使用完ThreadLocal后应手动调用remove()以防止内存泄漏。此外,还讨论了InheritableThreadLocal在线程间传递变量的场景及其区别。
摘要由CSDN通过智能技术生成

前言

1.为什么用 ThreadLocal?

所谓并发,就是有限资源需要应对远超资源的访问。解决问题的方法,要么增加资源应对访问;要么增加资源的利用率。 所以,相信这年头做开发的多多少少,都会那么几个“线程二三招”、“用锁五六式”。 那所带来的就是多线程访问下的并发安全问题。 共享变量的访问域跨越了原始的单线程,进入了千家万户的线程眼里。谁都可以用,谁都可以改,那不就打起来了吗? 因此,防止并发问题的最好办法,就是不要多线程访问(这科技水平倒退二十年~)。ThreadLocal 顾名思义,将一个变量限制为“线程封闭”:对象只被一个线程持有、访问、修改。

2.那到底什么是 ThreadLocal?

ThreadLocal 如果做到线程封闭,那固然是独木难支。它必然携手 Thread 为广大 Javaer 带来福音。 ThreadLocal 自己不是存储者,它只是 Thread 的搬运工。独有变量必然是存在 Thread 中的。一般项目中多定义多个 ThreadLocal,那相应的 Thread 必然也需要存储那么多独有变量。 既然解决了线程之间的访问干扰,那一个线程的访问干扰自然就不在话下了。Thread 维护了一个 ThreadLocalMap,以“key-value”的形式存储了独有变量;以 ThreadLocal 实例为 key,精准获取。

3.ThreadLocal 需要考虑哪些问题?

如果线程死亡了,那 ThreadLocalMap、ThreadLocal 及独有变量都会被销毁。

但是现在避免线程的重复创建与销毁,线程使用完都是放回线程池。而如果没有手动移除 ThreadLocalMap 的元素,即使当前线程退出,ThreadLocal 已不被线程方法栈持有,也依然无法被回收,从而造成内存泄漏。 所以 ThreadLocalMap.Entry 的 key(也就是 ThreadLocal)实际是弱引用。当没有其他强引用时,只要发生 GC,就会被回收,相当于这个时候 key 为 null。

这又产生了一个问题,key 被回收了, entry 和 value 可还是强引用呢,怎么办? ThreadLocalMap 已经考虑了这种情况,再调用 set()、get()、remove() 方法的时候,会清理掉 key 为 null 的记录。 所以人家设计是没有问题的,如果发生内存泄漏都是用的不对。 建议使用完 ThreadLocal方法后,最好手动调用remove()方法。

4.ThreadLocal 还需要考虑哪些问题?

随着业务场景的复杂化,变量的线程封闭固然解决了访问的问题,但是也给线程传递带来了难度。 线程之间的协作,带来了变量在两个线程之间安全传递的需要。需要人为处理这种传递,需要三个步骤:

  • 线程 1 取出变量;
  • 线程 1 安全传递变量、ThreadLocal(其实一般选择共享)给线程 2,当心逃逸。
  • 线程 2 放到当前线程的 ThreadLocal。 这个步骤是通用的,只要存在使用 hreadLocal并且需要线程传递时,必然少不了这三步。 JDK 为我们提供了“线程 2 是线程 1 创建出来时,独有变量传递给线程 2”的解决方法:InheritableThreadLocal,Thread 中也有专门为其服务的 ThreadLocalMap。

那我们明白,在线程池化的世面下,不会经常存在创建的场景,更多的是与已有线程的协作。 各家公司,其实也会为相关业务的 ThreadLocal 自研类库,去做到传递。 市面上解决通用场景的线程传递的类库就是 TransmittableThreadLocal。

源码解析

Thread

public Class Thread implements Runnable {

    //与此线程有关的 ThreadLocal 值。由 ThreadLocal 类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;
    // 与此线程有关的 InheritableThreadLocal 值。由 InheritableThreadLocal 类维护
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ThreadLocalMap 是 ThreadLocal 的内部类,是定制的 Map 实现。 初始值都为 null,只有当第一次调用对应ThreadLocal的 get 或 set 时,才会初始化。

ThreadLcoal

ThreadLcoal 只有一个默认的无参构造函数。实际的初始化逻辑,都在第一次调用 get 或 set 时。

get()

由于是类似懒加载的形式,所以 get 中涉及到ThreadLocalMap的创建以及初始值设置。

public T get() {
    Thread t = Thread.currentThread();
    // 获取线程的 map, 为啥要抽取方法呢?就是为了扩展之前提到的 InheritableThreadLocal
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    // 已经 set 过
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
    @SuppressWarnings("unchecked")
    // 走到这里没有 Entry 的情况:remove 以后
    T result = (T)e.value;
    return result;
    }
    }
    // 未 set 过的第一次 get (map == null)
    // 或 set 过, 但是 remove 了 (map != null && e == null)
    return setInitialValue();
}

private T setInitialValue() {
    // 获取指定初始值, 默认是 null
    // 可以通过 withInitial(Supplier<? extends S> supplier) 工厂方法来创建指定初始化值的 ThreadLocal
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        // ThreadLocalMap 未初始化
        createMap(t, value);
    }
    if (this ins
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值