ThreadLocal 总结

简介: 当工作于多线程中的对象使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中 “Local” 所要表达的意思。

什么是ThreadLocal


ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。


当工作于多线程中的对象使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程分配一个独立的变量副本。


所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中 “Local” 所要表达的意思。

ThreadLocal 提供了线程的局部变量副本,每个线程都可以通过set()get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程的数据隔离~。

其实就是你创建了一个 Threadlocal 变量,每个访问 Threadlocal 变量的线程都有一个本地副本。


往ThreadLocal 中填充的变量属于当前线程,该变量对其他线程而言是隔离的。


ThreadLocal数据结构


image.png


一个 ThreadLocal 只能存储一个 Object 对象,如果需要存储多个 Object 对象那么就需要多个 ThreadLocal


image.png


  • ThreadLocalMap 的 Entry 对 ThreadLocal 的引用为弱引用,避免了 ThreadLocal 对象无法被回收的问题
  • ThreadLocalMap 的 set 方法通过调用 replaceStaleEntry 方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏

复习一下java的对象引用


• 强引用:new 出来的一般对象,只要引用在就不会被回收

• 软引用: 将要发生内存溢出之前回收

• 弱引用: 生存到下一次垃圾收集发生之前

• 虚引用:目的是对象被收集器回收时收到一个系统通知

threadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。


ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收


Threadlocal 的 key 是弱引用,那么在 threadlocal.get() 的时候,发生 GC 之后,key 是否是 null ?


做 threadlocal.get() 操作,证明其实还是有强引用存在的。所以 key 并不为 null 。

ThreadLocal 为什么会造成内存泄漏?


ThreadLocalMap 使用 ThreadLocal 的弱引用作为key,如果一个 ThreadLocal 没有外部强引用来引用它,那么系统 GC 的时候,这个 ThreadLocal 势必会被回收,这样一来,ThreadLocalMap 中就会出现key为 null 的 Entry,就没有办法访问这些key 为null 的 Entry 的 value ,如果当前线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链:


Thread Ref -> Thread -> ThreaLocalMap -> Entry ->value


永远无法回收,造成内存泄漏。

简单来说,就是因为 ThreadLocalMap 的 key 是弱引用,当 TheadLocal 外部没有强引用时,就被回收,此时会出现 ThreadLocalMap<null,value> 的情况,而线程没有结束的情况下,导致这个 null 对应的 value 一直无法回收,导致泄漏。

ThreadLocal 内存泄漏的根源是:由于 ThreadLocalMap 的生命周期跟 Thread一样长,如果没有手动删除对应 key 就会导致内存泄漏,而不是因为弱引用。


ThreadLocal 类型变量为何声明为 static  ?

ThreadLocal 类的目的是为每个线程单独维护一个变量的值,避免线程间对同一变量的竞争访问,适用于一个变量在每个线程中需要有自己独立的值的场合。


如果把 threadLocalID 声明为非静态,则在含有 ThreadLocal 变量的的每个实例中都会产生一个新对象,这是毫无意义的,只是增加了内存消耗。


ThreadLocal的最佳实践


  • ThreadLocal 并不解决多线程 共享 变量的问题
  • 如果要同时满足变量在线程间的隔离与方法间的共享,ThreadLocal 再合适不过
  • 保存线程上下文信息,在任意需要的地方可以获取
  • 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失
  • 应该在我们不使用的时候,主动调用 remove 方法进行清理。


try {
    // 其它业务逻辑
} finally {
    threadLocal对象.remove();
}


InheritableThreadLocal


ThreadLocal  固然很好,但是子线程并不能取到父线程的 ThreadLocal 的变量:


private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        integerThreadLocal.set(1001); // father
        new Thread(() -> System.out.println(Thread.currentThread().getName() + ":"
                + integerThreadLocal.get())).start();
    }
//output:
Thread-0:null


使用 ThreadLocal 不能继承父线程的 ThreadLocal 的内容,而使用 InheritableThreadLocal 时可以做到的,这就可以很好的在父子线程之间传递数据了。i
nheritableThreadLocal 继承了 ThreadLocal。


 private static InheritableThreadLocal<Integer> inheritableThreadLocal =
            new InheritableThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        inheritableThreadLocal.set(1002); // father
        new Thread(() -> System.out.println(Thread.currentThread().getName() + ":"
                + inheritableThreadLocal.get())).start();
    }
//output:
Thread-0:1002


其他 ThreadLocal 实现


Netty 的 FastThreadLocal


       对 JDK 中 ThreadLocal 进行优化,由于 ThreadLocal 底层存储数据是一个 ThreadLocalMap  结构,是一个数组结构,通过 threadLocalHashCode 查找在数组中的元素 Entry ,  当 hash 冲突时,继续向前检测查找, 所以当 Hash 冲突时,检索的效率就会降低。而 FastThreadLocal 则正是处理了这个问题,使其时间复杂度一直为O(1)。


TransmittableThreadLocal:


       TransmittableThreadLocal 是Alibaba开源的、用于解决 在使用线程池等会缓存线程的组件情况下传递 ThreadLocal 问题的 InheritableThreadLocal 扩展。


       JDK 的 InheritableThreadLocal 类可以完成父线程到子线程的值传递。


       但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal 值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的 ThreadLocal 值传递到 任务执行时。


       原理是使用 TtlRunnable/Ttlcallable包装了 Runnable/Callable 类。


注意


spring 框架内部很多地方使用 ThreadLocal 来辅助实现,如事务管理。


但是Spring 根本就没有对 bean 的多线程安全问题做出任何保证与措施。


对于每个bean 的线程安全问题,根本原因是每个 bean 自身的设计。


不要在 bean 中声明任何有状态的实例变量或类变量,如果必须如此,那么就使用 ThreadLocal把变量变为线程私有的,如果 bean 的实例变量或类变量需要在多个线程之间共享,那么就只能使用 synchronized、lock、CAS 等这些实现线程同步的方法了。

相关文章
|
消息中间件 Java 中间件
秒懂消息队列MQ,万字总结带你全面了解消息队列MQ
消息队列是大型分布式系统不可缺少的中间件,也是高并发系统的基石中间件,所以掌握好消息队列MQ就变得极其重要。接下来我就将从零开始介绍什么是消息队列?消息队列的应用场景?如何进行选型?如何在Spring Boot项目中整合集成消息队列。
23865 10
秒懂消息队列MQ,万字总结带你全面了解消息队列MQ
|
SQL 存储 API
Flink实践:通过Flink SQL进行SFTP文件的读写操作
虽然 Apache Flink 与 SFTP 之间的直接交互存在一定的限制,但通过一些创造性的方法和技术,我们仍然可以有效地实现对 SFTP 文件的读写操作。这既展现了 Flink 在处理复杂数据场景中的强大能力,也体现了软件工程中常见的问题解决思路——即通过现有工具和一定的间接方法来克服技术障碍。通过这种方式,Flink SQL 成为了处理各种数据源,包括 SFTP 文件,在内的强大工具。
371 15
|
6月前
|
存储 架构师 安全
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
锁状态bits1bit是否是偏向锁2bit锁标志位无锁状态对象的hashCode001偏向锁线程ID101轻量级锁指向栈中锁记录的指针000重量级锁指向互斥量的指针010尼恩提示,讲完 如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等优化手段 , 可以得到 120分了。如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等‌。JVM锁的膨胀、锁的内存结构变化相关的面试题,是非常常见的面试题。也是核心面试题。
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
|
前端开发 JavaScript
前端模拟接口工具推荐——Apifox(mock数据)【图解教程】
前端模拟接口工具推荐——Apifox(mock数据)【图解教程】
3176 1
|
11月前
|
Java Maven Spring
如何在idea中创建Springboot项目? 手把手带你创建Springboot项目,稳!
文章详细介绍了在IDEA中创建Spring Boot项目的过程,包括选择Spring Initializr、配置项目属性、选择Spring Boot版本、导入依赖、等待依赖下载以及项目结构简介。
10605 1
|
11月前
|
数据安全/隐私保护
github报错(完美解决):获取token。remote: Support for password authentication was removed on August 13, 2021.
这篇文章介绍了如何在GitHub上解决因密码认证被移除而导致的推送错误,通过创建和使用个人访问令牌(token)来代替密码进行身份验证。
2399 0
|
算法 Java Sentinel
限流算法(计数器、滑动时间窗口、漏斗、令牌)原理以及代码实现
> 本文会对这4个限流算法进行详细说明,并输出实现限流算法的代码示例。 > 代码是按照自己的理解写的,很简单的实现了功能,还请大佬们多多交流找bug。
1609 0
|
负载均衡 Java 应用服务中间件
|
存储 缓存 前端开发
JVM(二):Class加载机制
JVM(二):Class加载机制