ThreadLocal的深度解读

2023-05-16

一、J2SE的原始描述

This class provides thread-local variables.  These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.  ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

该类提供线程局部变量。这些变量与普通变量的不同之处在于,每个线程访问的变量(通过其get或set方法)都是自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的private static字段,希望将有状态变化的对象与线程关联(例如用户ID或事务ID)。

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible;  after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

只要线程是活的,并且ThreadLocal实例是可访问的,每个线程都持有对其线程局部变量副本的隐式引用,即弱引用(除非存在对这些副本的其他引用)。

二、ThreadLocal解读

ThreadLocal线程变量,即线程局部变量,该ThreadLocal的变量只属于当前线程独享,对其他线程而言是隔离的。ThreadLocal在每个线程中都创建一个副本,每个线程只访问自己的副本。

ThreadLocal 变量通常被private static修饰,因此是同一个对象,并且是同一个 ThreadLocal 对象在不同的 Thread 中创建不同的副本。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

三、ThreadLocal示例

package com.observer;

public class ThreadLocalTest {

	private static ThreadLocal<String> localVar = new ThreadLocal<String>();

	public static void print(String str) {
		// 打印当前线程中本地内存中本地变量的值
		System.out.println(str + " :" + localVar.get());
		// 清除本地内存中的本地变量
		localVar.remove();
	}

	public static void main(String[] args) throws InterruptedException {

		new Thread(new Runnable() {
			public void run() {
				for (int i = 1; i <= 5; i++) {
					ThreadLocalTest.localVar.set("local_A--" + i);
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					print("A-" + i);
					// 清楚后打印
					System.out.println("A-" + i + "---after remove : " + localVar.get());
				}
			}
		}, "A").start();

		new Thread(new Runnable() {
			public void run() {
				for (int i = 1; i <= 5; i++) {
					ThreadLocalTest.localVar.set("local_B--" + i);
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					print("B-" + i);
					// 清楚后打印
					System.out.println("B-" + i + "---after remove : " + localVar.get());
				}
			}
		}, "B").start();
	}
}

打印输出:
A-1 :local_A--1
A-1---after remove : null
B-1 :local_B--1
B-1---after remove : null
A-2 :local_A--2
A-2---after remove : null
B-2 :local_B--2
B-2---after remove : null
A-3 :local_A--3
A-3---after remove : null
B-3 :local_B--3
B-3---after remove : null
A-4 :local_A--4
A-4---after remove : null
B-4 :local_B--4
B-4---after remove : null
A-5 :local_A--5
A-5---after remove : null
B-5 :local_B--5
B-5---after remove : null

示例中,可以看到两个线程只获取了自己线程存放的变量,他们之间变量是隔离的,互不干扰。

四、ThreadLocal的原理

4.1、set方法

public void set(T value) {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,
        //则直接更新要保存的变量值,否则创建threadLocalMap,并赋值
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // 初始化thradLocalMap 并赋值
            createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocal.ThreadLocalMap threadLocals = null;

static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        .....
}

从代码可以看到,ThreadLocal调用set方法赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化。 

ThreadLocalMap是ThreadLocal的内部静态类,而它的构成主要是用Entry来保存数据,使用ThreadLocal对象作为key,使用我们设置的value作为value。

Threadlocal是当前线程中属性ThreadLocalMap集合中的某一个Entry的key值,不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独立的、隔离的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量引用地址是一样的。如下图所示:

4.2、get方法

public T get() {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //3、如果map数据不为空,
        if (map != null) {
            //3.1、获取threalLocalMap中存储的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
        return setInitialValue();
}
 
 
private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
}

4.3、remove方法

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
 }

remove方法,直接将ThrealLocal 对应的值从当前Thread中的ThreadLocalMap中删除。

为什么要删除?这涉及到内存泄露的问题

ThreadLocalMap中的Entry继承了弱应用WeakReference,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉,Entry空键(即entry.get())== null)表示该键不再被引用,因此该Entry可以被垃圾回收掉。ThreadLocal其实是与线程绑定的一个变量,如果没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将会与线程共存。通常线程池中对线程管理都是采用线程复用的方法,在线程池中线程很难结束甚至于永远不会结束,这将意味着线程持续的时间将不可预测,甚至与JVM的生命周期一致。举个例字,如果ThreadLocal中直接或间接包装了集合类或复杂对象,每次在同一个ThreadLocal中取出对象后,再对内容做操作,那么内部的集合类和复杂对象所占用的空间可能会开始持续膨胀。

五、ThreadLocal的应用

ThreadLocal 适用于如下两种场景

  • 1、每个线程需要有自己单独的实例
  • 2、实例需要在多个方法中共享,但不希望被多线程共享

5.1、数据跨层跨方法调用(controller,service, dao)

        每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取的用户信息),可以让不同方法直接使用,避免参数传递的麻烦却不想被多线程共享(因为不同线程获取到的用户信息不一样)。最常见的是用 ThreadLocal 保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID 等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。在线程生命周期内,都通过这个静态 ThreadLocal 实例的 get() 方法取得自己 set 的那个对象,避免了将这个对象(如 user 对象)作为参数传递的麻烦。

5.2、数据库连接,处理数据库事务

5.3、Spring使用ThreadLocal解决线程安全问题

一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,Bean默认的作用域为singleton(单例)。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的“状态性对象”采用ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因此有状态的Bean就能够以singleton的方式在多线程中正常运行。

Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9-2所示。 

 这样用户就可以根据需要,将一些非线程安全的有状态的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有对象所访问的同一ThreadLocal变量都是当前线程所绑定的。

六、使用ThreadLocal注意事项

1、将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值


 2、每次使用完ThreadLocal,都调用它的remove()方法,清除数据,防止内存泄露。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ThreadLocal的深度解读 的相关文章

  • Java ThreadLocal

    ThreadLocal是什么 xff1f 定义 xff1a 提供线程局部 变量 xff1b 一个线程局部边用在多个线程中分别有独立的值 xff08 副本 xff09 特点 xff1a 简单 xff08 开箱即用 xff09 快速 xff08
  • Linux SWAP 深度解读

    概述 本文讨论的swap基于Linux4 4内核代码 Linux内存管理是一套非常复杂的系统 xff0c 而swap只是其中一个很小的处理逻辑 希望本文能让读者了解Linux对swap的使用大概是什么样子 阅读完本文 xff0c 应该可以帮
  • ThreadLocal 适合用在哪些实际生产的场景中?

    在通常的业务开发中 xff0c ThreadLocal有两种典型的使用场景 场景1 xff0c ThreadLocal 用作保存每个线程独享的对象 xff0c 为每个线程都创建一个副本 xff0c 这样每个线程都可以修改自己所拥有的副本 而
  • ThreadLocal的深度解读

    一 J2SE的原始描述 This class provides thread local variables These variables differ from their normal counterparts in that eac
  • 深入理解ThreadLocal源码

    1 预备知识 强软弱虚引用 在Java中有四种引用的类型 强引用 软引用 弱引用 虚引用 设计这四种引用的目的是可以用程序员通过代码的方式来决定对象的生命周期 方便GC 强引用 强引用是程序代码中最广泛使用的引用 如下 Object o n
  • InheritableThreadLocal类详解

    我们在使用ThreadLocal类的时候 可以保证各个线程使用自己的数据 而不相互干扰 但是如果我们有这样的一个需求 就是各个线程相互不干扰的情况下 各个线程的子线程可以访问到当前线程中的值 对于这个子线程来说就是访问父线程 public
  • ThreadLocal和ThreadLocalMap

    1 ThreadLocal是什么 是用来存放我们需要能够线程隔离的变量的 那就是线程本地变量 也就是说 当我们把变量保存在ThreadLocal当中时 就能够实现这个变量的线程隔离了 entry中的key使用了弱引用 static clas
  • C++11中thread_local的使用

    C 11中的thread local是C 存储期的一种 属于线程存储期 存储期定义C 程序中变量 函数的范围 可见性 和生命周期 C 程序中可用的存储期包括auto register static extern mutable和thread
  • ThreadLocal 是否优于 HttpServletRequest.setAttribute("key", "value")?

    Servlet 规范 请参阅我之前的问题 保证同一个线程将执行所有 Filter 和关联的 Servlet 鉴于此 我认为使用传递数据没有任何用处HttpServletRequest setAttribute如果可以选择使用ThreadLo
  • 如何清理ThreadLocals

    有人有一个如何做到这一点的例子吗 它们是由垃圾收集器处理的吗 我正在使用 Tomcat 6 javadoc 是这样说的 只要线程处于活动状态并且 ThreadLocal 实例可访问 每个线程就持有对其线程局部变量副本的隐式引用 线程消失后
  • Python 中的“线程本地存储”是什么,为什么需要它?

    具体来说 在 Python 中 变量如何在线程之间共享 虽然我用过threading Thread在此之前我从未真正理解或看到变量如何共享的示例 它们是在主线程和子线程之间共享还是仅在子线程之间共享 我什么时候需要使用线程本地存储来避免这种
  • ThreadLocal - 用作带有 spring-boot 的 REST API 的上下文信息

    我有一些spring boot应用程序 它公开了 REST API 提到的 REST API 是由spring security 一切都很好 但是现在我需要设置上下文 用于服务请求 设置上下文是指根据用户上下文选择数据源 关键是Routin
  • 使用 ThreadLocal 作为数据上下文是个好主意吗?

    使用 ThreadLocal 作为 Web 应用程序中数据的上下文是个好主意吗 这就是它的目的 但请注意删除上下文末尾的 ThreadLocal 否则可能会出现内存泄漏 或者至少会保留未使用的数据太长时间 ThreadLocals 也非常快
  • EJB 容器中的 ThreadLocal(和 Singleton)

    我编写了一个授权系统 它依赖于代表当前用户的对象 为了简化编程并提高性能 我想在用户登录后将这些对象保存在 ThreadLocal 中 它看起来像这样 public class UserCache private static final
  • 在并行任务期间跟踪失效的 WebDriver 实例

    我看到一些使用 Selenium WebDriver 运行并行嵌套循环 Web 压力测试的死实例怪异现象 简单的例子是 比如说 点击 300 个独特的页面 每个页面有 100 次展示 我 成功 获得 4 8 个 WebDriver 实例Th
  • 为什么 Java 语言设计者对于大多数基于散列的结构(除了 ThreadLocal 之类的结构之外)更喜欢使用链接而不是开放寻址? [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我知道解决哈希冲突的开放寻址和链接之
  • Java 的 ThreadLocal 底层是如何实现的?

    ThreadLocal是如何实现的 它是用 Java 实现的 使用一些从 ThreadID 到对象的并发映射 还是使用一些 JVM 钩子来更有效地完成它 这里的所有答案都是正确的 但有点令人失望 因为它们在某种程度上掩盖了如何聪明Threa
  • 为什么 ThreadLocal 实用程序在 Spring MVC 应用程序中总是返回 null?

    我编写了这个实用程序类来在 Spring MVC 应用程序中保存临时数据 public abstract class FooUtil private static final ThreadLocal
  • 变量的同步和本地副本

    我正在查看一些具有以下习惯用法的遗留代码 Map
  • Python 中上下文相关的日志级别

    我正在用 Python 制作一个 Web 应用程序框架的原型 主要是为了教育目的 但我一直坚持一个我一直想要的功能 每条路由的日志级别 此功能的目标是识别我们正在执行诊断的一些特定入口点 例如 我想跟踪呼叫者拨打电话时发生的情况POST s

随机推荐

  • gstreamer学习(一) gstreamer-rtsp-server环境安装

    gstreamer rtsp server环境安装 Linux环境下 两种方式 xff1a 第一种方式 xff0c 通过官网安装 xff08 如果是Linux环境 xff0c 可以直接通过软件包工具进行安装 xff09 xff0c 点击进入
  • 用C++打开指定网址

    用C 43 43 打开指定网址原理 system 命令 就像这样 xff1a span class token macro property span class token directive hash span span class t
  • 项目遇到的各种异常抛出及解决方法

    项目遇到的各种异常抛出及解决方法 xff1a 1 java lang NumberFormatException xff1a 类型格式异常 第一次遇到的异常抛出原因及解决方法 xff1a 项目运行没有问题 xff0c 各种接口能正常查询出数
  • 【STC8学习笔记】STC8A8K64S4A12精准延时函数设置

    在设置单片机精准的延时函数的时候 xff0c 给大家一个方法 xff0c STC ISP有一个延时函数计算器 xff0c 可以计算出想要的延时 我的例程也是基于这个软件生成的 xff0c 我生成一个1ms和1us出来 xff0c 剩下的我再
  • vc版本与vs版本对应关系

    vc版本与vs版本对应关系 最近在整理之前代码 xff0c 用cmake编译一直报错 xff0c 忘记了opencv3 1 0不支持vs2019 xff0c 所以在这里总结下vc版本与vs版本对应关系 VC版本号 VS对应版本 vc6 VC
  • cmake编译依赖opencv的c++库

    前面一篇主要讲了c 43 43 项目怎么在本地配置opencv过程 xff0c 这种方式缺点就是只能在开发着本地环境编译 xff0c 换台电脑就会出现环境配置问题 接下来主要讲解 xff0c 使用cmake编译 xff0c 生成一个依赖op
  • c++ stl 迭代器iterators(traits编程技法)

    文章目录 1 1 迭代器设计思维 stl关键所在1 2 迭代器是一种smart pointer1 3 迭代器相应型别 xff08 associated types xff09 1 4 traits编程技法 stl源代码门匙1 4 1 val
  • 如何用算法把一个十进制数转为十六进制数-C语言基础

    这一篇文章要探讨的是 如何用算法实现十进制转十六进制 并不涉及什么特别的知识点 属于C语言基础篇 在翻找素材的时候 xff0c 发现一篇以前写的挺有意思的代码 xff0c 这篇代码里面涉及的知识点没有什么好讲的 xff0c 也没有什么特别的
  • 关于 Qt使用QJsonObject解析失败的问题。

    1 问题 在QJsonObject转 toInt toLongLong 等类型时 xff0c 转换失败 但是转toString xff08 xff09 没有任何问题 列如 xff1a 解决方法 xff1a 这样 xff0c 就可以结局问题
  • char 和 string 的相互转换

    一个char字符转为string span class token keyword char span ch span class token operator 61 span span class token char 39 A 39 s
  • C++STL标准库学习总结/索引/学习建议

    前言 xff1a 如果刚刚开始学习STL标准库 xff0c 不知道从哪里入手学习的话 xff0c 建议去中国大学mooc平台 xff0c 先学习北京大学郭炜老师的 程序设计与算法 xff08 一 xff09 C语言程序设计 xff08 ht
  • Python 调用API接口方式,通过http.client调用api接口,远程调用flask接口方式

    一 创建接口 xff08 如果调用别人的接口 xff0c 跳过此条 xff09 如果没有api xff0c 首先自己写一个接口玩一下 xff1a 必备知识 xff1a 一个项目最基本的文件 xff0c 接口run py文件 config文件
  • git tag和branch的区别

    tag 和branch的区别 Git tag是一系列commit的中的一个点 xff0c 只能查看 xff0c 不能移动 branch是一系列串联的commit的线 git tag的用法 我们常常在代码封板时 使用git 创建一个tag 这
  • 结构体对齐计算(超详细讲解,一看就会)

    想要计算结构体大小 xff0c 咱就先要清楚结构体内存对齐的规则 xff1a 1 结构体的第一个成员直接对齐到相对于结构体变量起始位置为0处偏移 2 从第二个成员开始 xff0c 要对齐到某个 对齐数 的整数倍的偏移处 3 结构体的总大小
  • RTK差分编码

    一 概念 DCB xff08 Differential Code Bias 差分码偏差 xff09 是全球卫星导航系统 xff08 GNSS xff09 中 xff0c 通过不同信号得到的观测值之间存在的系统性偏差 DCB是由卫星和接收机硬
  • 详解JAVA的事件监听机制和观察者设计模式

    一 事件监听机制的三要素 事件源 事件监听器 xff0c 事件对象 监听器一般是JAVA接口 xff0c 用来约定可以执行的操作 二 事件监听机制简要说明 事件源注册一个或者多个事件监听器 xff0c 事件源对象状态发生变化或者被操作时 x
  • Nginx控制IP(段)的访问策略配置

    Nginx engine x 是一个高性能的HTTP和反向代理web服务器 xff0c 同时也提供了IMAP POP3 SMTP服务 有着负载均衡 动静分离等强大的功能 xff0c 而且还有众多三方插件来满足应用要求 这里重点介绍nginx
  • 敏捷开发-互联网时代的软件开发方式

    一 什么是敏捷开发 敏捷开发简单的描述为 xff1a 是一种应对需求快速变化的软件开发方式 敏捷开发的核心思想就是小步快跑 不断迭代 xff0c 在一次次的迭代升级中完成 小目标 最终完成那个 大目标 正因为敏捷开发的这种不断迭代升级的开发
  • Window系统查看端口是否启用以及占用程序

    1 打开DOS命令行窗口 开始 gt 运行 gt cmd xff0c 或者是 window 43 R gt cmd xff0c 调出命令窗口 2 查看当前正在使用的所有端口 命令 xff1a netstat ao 包括协议 xff0c 端口
  • ThreadLocal的深度解读

    一 J2SE的原始描述 This class provides thread local variables These variables differ from their normal counterparts in that eac