首页 > 其他分享 >ThreadLocal

ThreadLocal

时间:2024-02-07 17:07:13浏览次数:18  
标签:ThreadLocalMap ThreadLocal 实例 线程 Key Entry

(ThreadLocal)

什么是ThreadLocal?

ThreadLocal 被译为==线程本地变量”类==,在 Java 的多线程并发执行过程中,为保证多个线程对变量的安 全访问,可以将变量放到ThreadLocal 类型的对象中,使变量在每个线程中都有独立值,不会出现一个 线程读取变量时而被另一个线程修改的现象。

ThreadLocal 是解决线程安全问题一个较好方案,它通过为每个线程提供一个独立的本地值,去解决并 发访问的冲突问题。很多情况下,使用 ThreadLocal 比直接使用同步机制(如 synchronized)解决线 程安全问题更简单,更方便,且结果程序拥有更高的并发性。 举例:

  • ThreadLocal在Spring中作用巨大,在管理Request作用域中的Bean、事务、任务调度、AOP等模 块都有它。
  • Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态 的Bean就能够以singleton的方式在多线程中正常工作了。

ThreadLocal 使用场景

ThreadLocal 使用场景大致可以分为以下两类:

1. 解决线程安全问题

ThreadLocal 的主要价值在于解决线程安全问题, ThreadLocal 中数据只属于当前线程,其本地值对别 的线程是不可见的,在多线程环境下,可以防止自己的变量被其他线程篡改。另外,由于各个线程之间 的数据相互隔离,==避免同步加锁带来的性能损失==,大大提升了并发性的性能。 ==典型案例:可以每个线程绑定一个数据库连接,是的这个数据库连接为线程所独享,从而避免数据库连 接被混用而导致操作异常问题。==

private static final ThreadLocal localSqlSession = new ThreadLocal();
public void startManagedSession() {
    this.localSqlSession.set(openSession());
}
@Override
public Connection getConnection() {
    final SqlSession sqlSession = localSqlSession.get();
    if (sqlSession == null) {
        throw new SqlSessionException("Error:  Cannot get connection.  No
                managed session is started.");
    }
    return sqlSession.getConnection();
}

2. 跨函数传递数据

通常用于同一个线程内,跨类、跨方法传递数据时,如果不用 ThreadLocal,那么相互之间的数据传递势必要靠返回值和参数,这样无形之中增加了这些类或者方法之间的耦合度。 在这里插入图片描述 “跨函数传递数据”场景典型案例:可以每个线程绑定一个 Session(用户会话)信息,这样一个线程的 所有调用到的代码,都可以非常方便地访问这个本地会话,而不需要通过参数传递。

//伪代码
public class SessionHolder{
    private static final ThreadLocal<UserDTO> sessionUserLocal = new
            ThreadLocal<>("sessionUserLocal");
    // session 线程本地变量
    private static final ThreadLocal<HttpSession> sessionLocal = new
            ThreadLocal<>("sessionLocal");
	//...省略其他 
	/**
	*保存 session 在线程本地变量中 */
	public static void setSession(HttpSession session){
        sessionLocal.set(session);
    }
	/**
	 * 取得绑定在线程本地变量中的 session
	 */
	public static HttpSession getSession() {
        HttpSession session = sessionLocal.get(); 
        Assert.notNull(session, "session 未设置"); return session;
    }
	//...省略其他 
}

底层原理

ThreadLocal内部结构演进:

早期ThreadLocal为一个 Map。当工作线程 Thread 实例向本地变量保持某个值时,会以“Key-Value 对” 的形式保存在 ThreadLocal 内部的 Map 中,其中 Key为线程 Thread 实例, Value 为待保存的值。当 工作线程 Thread 实例从 ThreadLocal 本地变量取值时,会以 Thread 实例为 Key,获取其绑定的 Value。 在这里插入图片描述 在 JDK8 版本中, ThreadLocal 的内部结构依然是Map结构,但是其拥有者为Thread线程对象,每一个 Thread 实例拥有一个ThreadLocalMap对象。Key 为 ThreadLocal 实例。

与早期版本的 ThreadLocalMap 实现相比,新版本的主要的变化为:

  • 拥有者发生了变化:新版本的 ThreadLocalMap 拥有者为 Thread,早期版本的ThreadLocalMap 拥有者为 ThreadLocal。
  • Key 发生了变化:新版本的 Key 为 ThreadLocal 实例,早期版本的 Key 为 Thread 实例。

与早期版本的 ThreadLocalMap 实现相比,新版本的主要优势为:

  • ThreadLocalMap 存储的“Key-Value 对”数量变少
  • Thread 实例销毁后, ThreadLocalMap 也会随之销毁,在一定程度上能减少内存的消耗。

Thread、ThreadLocal、ThreadLocalMap关系

Thread-->ThreadLocalMap-->Entry(ThreadLocalN,LocalValueN)*n

Entry 的 Key 为什么需要使用弱引用?

Entry 用于保存 ThreadLocalMap 的“Key-Value”条目,但是 Entry 使用了对 Threadlocal 实例进行包装之后的弱引用(WeakReference)作为 Key,其代码如下:

// Entry 继承了 WeakReference,并使用 WeakReference 对 Key 进行包装
static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value; //值
    Entry(ThreadLocal<?> k, Object v) {
        super(k); //使用 WeakReference 对 Key 值进行包装
        value = v;
    }
}

为什么 Entry 需要使用弱引用对 Key 进行包装,而不是直接使用 Threadlocal 实例作为 Key呢比如 如下代码

//伪代码
public void funcA() {
	//创建一个线程本地变量
	ThreadLocal local = new ThreadLocal(); //设置值
	local.set(100);
	//获取值
	local.get();
	//函数末尾
}

当线程n 执行 funcA 方法到其末尾时,线程n 相关的 JVM 栈内存以及内部 ThreadLocalMap成员的结 构,大致如图所示。 在这里插入图片描述 线程n 调用 funcA()方法新建了一个 ThreadLocal 实例,并使用 local 局部变量指向这个实例,并且 此 local 是强引用; 在调用 local .set(100)之后,线程n 的 ThreadLocalMap 成员内部会新建一个 Entry 实例,其 Key 以 弱引用包装的方式指向 ThreadLocal 实例。 当线程n 执行完 funcA 方法后, funcA 的方法栈帧将被销毁,强引用 local 的值也就没有了,==但此时线 程的 ThreadLocalMap 里的对应的 Entry 的 Key 引用还指向了 ThreadLocal 实例。== 若 Entry的 Key 引用是强引用,就会导致 Key 引用指向的 ThreadLocal 实例、及其 Value 值都不能被 GC回收,这将造成严重的内存泄露,具体如图所示。

在这里插入图片描述 由于 ThreadLocalMap 中 Entry 的 Key 使用了弱引用,在下次 GC 发生时,就可以使那些没有被其他强 引用指向、仅被 Entry 的 Key 所指向的 ThreadLocal 实例能被顺利回收。并且,在 Entry的 Key 引用被 回收之后,其 Entry 的 Key 值变为 null。后续当 ThreadLocal 的 get、 set 或 remove 被调用时,通过 expungeStaleEntry方法, ThreadLocalMap 的内部代码会清除这些 Key 为 null 的 Entry,从而完成相 应的内存释放。

标签:ThreadLocalMap,ThreadLocal,实例,线程,Key,Entry
From: https://blog.51cto.com/u_15323027/9638100

相关文章

  • 利用ThreadLocal优化获取用户基本信息
    //测试类packagecom.di.bigevent;importorg.junit.jupiter.api.Test;publicclassThreadLocalTest{@TestpublicvoidtestThreadLocalSetAndGet(){ThreadLocaltl=newThreadLocal();newThread(()->{tl.set("李星......
  • threadlocal 线程本地变量,线程独享
         ......
  • 服了,一个ThreadLocal被问出了花
    分享是最有效的学习方式。博客:https://blog.ktdaddy.com/故事地铁上,小帅无力地倚靠着杆子,脑子里尽是刚才面试官的夺命连环问,“用过TheadLocal么?ThreadLocal是如何解决共享变量访问的安全性的呢?你觉得啥场景下会用到TheadLocal?我们在日常用ThreadLocal的时候需要注意什么?Thr......
  • 使用ThreadLocal
    线程是Java实现多任务的基础,Thread对象代表一个线程,我们可以在代码中调用Thread.currentThread()获取当前线程。例如,打印日志时,可以同时打印出当前线程的名字:  对于多任务,Java标准库提供的线程池可以方便地执行这些任务,同时复用线程。Web应用程序就是典型的多任务应用,每个用......
  • Thread和ThreadLocal、ThreadLocalMap的关系
    ThreadLocal是什么 ThreadLocal官方注释: 翻译过来大致意思是:ThreadLocal可以提供局部变量,通过set和get方法对局部变量进行操作,并且局部变量是每个线程独立的、数据隔离的。ThreadLocal通常作为线程的私有的静态变量,用于和UserId、事务Id相关联。set方法:publicvoidse......
  • java中的ThreadLocal
    1.ThreadLocal的基本使用在Java的多线程并发执行过程中,为了保证多个线程对变量的安全访问,可以将变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立值,不会出现一个线程读取变量时而被另一个线程修改的现象。ThreadLocal类通常被翻译为线程本地变量类或者线程局部变......
  • ThreadLocal 在实战场景下的内存泄漏、逻辑混乱问题总结
    前言很早之前虽然看过ThreadLocal的源码,但是对于真实业务场景下可能存在的问题没有做过总结,刚好前几天在分析Mybatis内存泄漏的问题,想着ThreadLocal不是也可能会发生内存泄漏吗?于是乎本文出现了。本文相关博客1:ThreadLocal还存在内存泄漏?源码级别解读2:高质量实现单文件......
  • 深入理解Java中的ThreadLocal
    第1章:引言大家好,我是小黑。今天咱们来聊聊ThreadLocal。首先,让咱们先搞清楚,ThreadLocal是个什么玩意儿。简单说,ThreadLocal可以让咱们在每个线程中创建一个变量的“私有副本”。这就意味着,每个线程都可以独立地改变自己的副本,而不会影响其他线程。这就像是每个人都有自己的笔记......
  • Java中的ThreadLocal和 InheritableThreadLocal
    Java中的ThreadLocal和InheritableThreadLocalpackagecom.example.core.mydemo.java;/***output*Thread-0ThreadLocalvalue:null*Thread-0InheritableThreadLocalvalue:InheritableThreadLocalstring*/publicclassThreadLocalTest{publicstati......
  • ThreadLocal真的会造成内存泄漏吗?
    ThreadLoca在并发场景中,应用非常多。那ThreadLocal是不是真的会造成内存泄漏?今天给大家做一个分享,个人见解,仅供参考。1、ThreadLocal的基本原理简单介绍一下ThreadLocal,在多线程并发访问同一个共享变量的情况下,如果不做同步控制的话,就可能会导致数据不一致的问题,所以,我们需要使......