ThreadLocal源码解析 1.运行原理

news2025/1/20 14:59:27

ThreadLocal源码解析—运行原理

简介

ThreadLocal 类用来提供线程内部的局部变量,这种变量在多线程环境下访问(通过 get 和 set 方法访问)时能保证各个线程的变量相对独立于其他线程内的变量,分配在堆内的 TLAB 中。

ThreadLocal 实例通常来说都是 private static 类型的,属于一个线程的本地变量,用于关联线程和线程上下文。每个线程都会在 ThreadLocal 中保存一份该线程独有的数据,所以是线程安全的。

ThreadLocal 作用

  • 线程并发:应用在多线程并发的场景下。

  • 传递数据:通过 ThreadLocal 实现在同一线程不同函数或组件中传递公共变量,减少传递复杂度。

  • 线程隔离:每个线程的变量都是独立的,不会互相影响。

对比 synchronized:

synchronizedThreadLocal
原理同步机制采用以时间换空间的方式,
只提供了一份变量,让不同的线程排队访问
ThreadLocal 采用以空间换时间的方式,
为每个线程都提供了一份变量的副本,从而实现同时访问而相不干扰
侧重点多个线程之间访问资源的同步多线程中让每个线程之间的数据相互隔离

使用场景代码示例

/**
 * ThreadLocal通过为每一个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。
 * 
 * 该类提供了线程局部(thread-local)变量。这些变量不同于它们的普通对应物,
 * 因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量
 * 它独立于变量的初始化副本。ThreadLocal实例通常是类中的 private static 字段
 * 它们希望将状态与某一个线程(例如,用户ID 或 事务ID)相关联。
 * 
 * 例如,以下类生成对每个线程唯一的局部标识符。
 * 
 * 线程ID 是在第一次调用ThreadId.get()时分配的,
 * 在后续调用中不会更改。
 */
public class ThreadId {
    /**
     * 原子整数,一个分配给线程的 Thread ID
     */
    private static final AtomicInteger nextId = new AtomicInteger(0);

    /**
     * 每一个线程对应一个 Thread ID
     */
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
        /**
         * 初始化一个nextId的值
         */
        @Override
        protected Integer initialValue() {
            // nextId自增
            return nextId.getAndIncrement();
        }
    };

    /**
     * 返回当前线程对应的Thread ID,必要时会进行分配
     */
    public static int get() {
        return threadId.get();
    }

    // ------------------------测试程序------------------------
    public static void main(String[] args) throws InterruptedException {
        RunnableTask task = new RunnableTask();

        Thread t1 = new Thread(task, "线程1");
        t1.start();
        TimeUnit.MILLISECONDS.sleep(100);

        Thread t2 = new Thread(task, "线程2");
        t2.start();
        TimeUnit.MILLISECONDS.sleep(100);

        Thread t3 = new Thread(task, "线程3");
        t3.start();
        TimeUnit.MILLISECONDS.sleep(100);

        Thread t4 = new Thread(task, "线程4");
        t4.start();
        TimeUnit.MILLISECONDS.sleep(100);

        Thread t5 = new Thread(task, "线程5");
        t5.start();
        TimeUnit.MILLISECONDS.sleep(100);
    }
    
    static class RunnableTask implements Runnable {
        @Override
        public void run() {
            try {
                System.out.println("当前线程名称为:" + Thread.currentThread().getName() + ", 分配的id为:" + get());
            } finally {
                // 使用完毕之后,必须释放掉
                threadId.remove();
            }
        }
    }
}

执行结果

当前线程名称为:线程1,分配的Id为:0
当前线程名称为:线程2,分配的Id为:1
当前线程名称为:线程3,分配的Id为:2
当前线程名称为:线程4,分配的Id为:3
当前线程名称为:线程5,分配的Id为:4

从运行结果可以发现,不同的线程的数据是隔离的,一个线程只能获取本线程设置到 ThreadLocal 中的数据。

ThreadLocal 每个线程的变量 (nextId) 都是独立的,不会相互影响。

实现原理

底层结构

JDK8 以前:每个 ThreadLocal 都创建一个 Map,然后用线程作为 Map 的 key,要存储的局部变量作为 Map 的 value,达到各个线程的局部变量隔离的效果。这种结构会造成 Map 结构过大和内存泄露,因为 Thread 停止后无法通过 key 删除对应的数据。

image-20221105223520836

JDK8 以后:每个 Thread 维护一个 ThreadLocalMap,这个 Map 的 key 是 ThreadLocal 实例本身,value 是真正要存储的值。

// Thread类中的属性
ThreadLocal.ThreadLocalMap threadLocals = null;
  • 每个 Thread 线程内部都有一个 Map (ThreadLocalMap),是 ThreadLocal 的一个静态内部类。
  • Map 里面存储 ThreadLocal 对象(key)和线程的私有变量(value)。
  • Thread 内部的 Map 是由 ThreadLocal 维护的,由 ThreadLocal 负责向 map 获取和设置线程的变量值。
  • 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成副本的隔离,互不干扰。

image-20221105223558028

JDK8 设计方案的两个好处:

  • 每个 Map 存储的 Entry 数量会变少,因为之前的存储数量由 Thread 的数量决定,现在由 ThreadLocal 的数量决定,在实际编程当中,往往 ThreadLocal 的数量要少于 Thread 的数量。
  • 当 Thread 销毁之后,对应的 ThreadLocalMap 也会随之销毁,能减少内存的使用,防止内存泄露

内存结构示意图

image-20221105211449556

  • 每个线程持有一个 ThreadLocalMap 对象,是一个 Entry 数组。
  • 其中的 Entry 的 Key 是 ThreadLocal 对象,value 是对应存储的值。
  • 而一个 ThreadLocal 对象可以被多个不同的线程作为 ThreadLocalMap 的 Key。

image-20221105224839583

一句话理解 ThreadLocal:ThreadLocal 是作为当前线程中属性 ThreadLocalMap 集合中的某一个 Entry 的 key 值 Entry<ThreadLocal, value>,虽然不同的线程之间 ThreadLocal 这个 key 值是一样,但是不同的线程所拥有的 ThreadLocalMap 是独一无二的,也就是不同的线程间同一个 ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个 value 变量地址是一样的。

源码解析

属性及构造方法

public class ThreadLocal<T> {
    /*
     * 线程获取threadLocal.get()时,如果是第一次在某个ThreadLocal对象上get时,会给当前线程分配一个value,
     * 这个value 和当前的threadLocal对象被包装成为一个Entry,
     * 其中key是threadLocal对象,value是threadLocal对象给当前线程生成的value,
     *    --- Entry<ThreadLocal, Value> ---
     * 这个Entry存放到当前线程threadLocals这个map的哪个桶位?与当前threadLocal对象的threadLocalHashCode有关,
     * 使用threadLocalHashCode & (table.length - 1)得到的索引位置,就是当前Entry需要存放的位置。
     */
    private final int threadLocalHashCode = nextHashCode();

	/*
	 * 创建ThreadLocal对象时会使用到,每创建一个threadLocal对象,就会使用nextHashCode 分配一个hash值给这个对象。
	 */
    private static AtomicInteger nextHashCode = new AtomicInteger();

    /*
     * 每为线程创建一个ThreadLocalMap对象,这个ThreadLocal.nextHashCode这个值就会增长0x61c88647,这个值很特殊,它是斐波那契数(黄金分割法)。
     * hash增量为这个数字,带来的好处就是hash分布非常均匀。
     */
    private static final int HASH_INCREMENT = 0x61c88647;
    
	/*
     * 调用顺序:
     * threadLocalHashCode => nextHashCode() => nextHashCode => HASH_INCREMENT
     */
    
    /*
     * 创建新的ThreadLocal对象时 会给当前对象分配一个hash,使用这个方法。 
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

	/*
	 * 默认返回null,一般情况下需要重写该方法
	 */
    protected T initialValue() {
        return null;
    }

	// 构造方法
    public ThreadLocal() {
    }   

ThreadLocal.get()

// -----------------------ThreadLocal.get()------------------------------------
	/*
	 * 返回当前线程与当前ThreadLocal对象相关联的 线程局部变量,这个变量只有当前线程能访问到。
     * 如果当前线程 没有分配,则给当前线程去分配(使用initialValue方法)
	 */
    public T get() {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程内部的threadLocals(getMap(t) => t.threadLocals)也就是threadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // map不为null,说明当前线程已经拥有自己的ThreadLocalMap对象了
        if (map != null) {
            /*
             * 根据key拿到Entry
             * ThreadLocalMap的Entry的key就是ThreadLocal,所以这里传入this(ThreadLocal对象)尝试获取当前ThreadLoacl关联的Entry
             */
            // ThreadLocalMap.getEntry()方法 在下一篇ThreadLocalMap内核分析
            ThreadLocalMap.Entry e = map.getEntry(this);
            // e不为null,说明当前线程初始化过了与当前ThreadLocal对象相关联的线程局部变量
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                // 直接返回对应的value即可
                return result;
            }
        }

        /*
         * 执行到这里有几种情况?
         * 1.当前线程对应的threadLoaclMap为null,
         * 2.当前线程与当前threadLocal对象没有生成过相关联的线程局部变量(Entry为null)
         */

        // setInitialValue():初始化当前线程与当前threadLocal对象相关联的value
        // 且当前线程内部的threadLoaclMap没有创建的话,还会初始化创建Map
        return setInitialValue();
    }    

// ----------------------------ThreadLocal.getMap()----------------------------

    /**
     * threadLocals是Thread类内部的一个属性,
     * (ThreadLocal.ThreadLocalMap threadLocals;)
     * 可以理解为就是一个Map<ThreadLocal, Value> 
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

ThreadLocal.setInitialValue()

// --------------------------ThreadLocal.setInitialValue()-------------------
	/*
     * setInitialValue():初始化当前线程与当前threadLocal对象相关联的value
     * 且当前线程内部的threadLoaclMap没有创建的话,还会初始化创建Map
     */
	private T setInitialValue() {
        /*
         * 调用当前ThreadLocal对象的initialValue()方法,这个方法,大部分情况我们都会重写,
         * value就是当前ThreadLocal对象与当前线程相关联的线程局部变量。
         */
        T value = initialValue();
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取线程内部的threadLocals => threadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // map不为null,说明当前线程已经初始化过threadLocalMap对象了(线程的threadLocals只会初始化一次)
        if (map != null)
            // 保存当前threadLocal与当前线程生成的线程局部变量
			// key -> 当前threadLocal对象,value -> value
            // ThreadLocalMap.set()方法 在下一篇ThreadLocalMap内核分析
            map.set(this, value);
        else
            /*
             * 执行到这里说明当前线程内部的threadLocalMap对象还未初始化,执行createMap()方法创建threadLocalMap。
             * 参数1:当前线程 
             * 参数2:value
             */            
            createMap(t, value);
        // 返回线程与当前threadLocal相关的局部变量
        return value;
    }

// --------------------------ThreadLocal.createMap()--------------------------
	/*
     * 创建threadLocalMap并赋值给当前线程的threadLocals
     */ 
	void createMap(Thread t, T firstValue) {
    	/*
    	 * 传递t的意义就是 要访问当前这个线程的t.threadLocals字段,给这个字段初始化。
    	 * 为线程t内部的 threadLocals赋值 创建一个ThreadLocalMap对象
    	 * this:当前threadLocal对象
    	 * firstValue:value。
    	 */	    	
        // ThreadLoacalMap的构造方法 在下一篇文章ThreadLocalMap内核分析
        t.threadLocals = new ThreadLocalMap(this, firstValue);
	}

get() 方法流程图

  • 由图可知,调用 get 时如果当前线程内部的 map 未被创建或者 map 中没有当前 threadLocal 对象对应的 Entry,那么最终也会创建出 Map 并且插入相应的Entry。

image-20221106192912193

ThreadLocal.set()

    /*
     * 修改当前线程与当前threadLocal对象相关联的 线程局部变量。
     */
	public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程内部的threadLocals(threadLocalMap)
        ThreadLocalMap map = getMap(t);
        // 条件成立:说明当前线程的threadLocalMap已经初始化了
        if (map != null)
            // 重写 或者 添加value
            // ThreadLocalMap.set()方法 在下一篇ThreadLocalMap内核分析
            map.set(this, value);
        else
            // 创建线程内部的threadLocalMap并设置value
            createMap(t, value);
    }

ThreadLocal.set() 方法流程图

在这里插入图片描述

ThreadLocal.remove()

  • 将当前线程内部的 ThreadLocalMap 中 key 为当前 threadLocal 对象的 Entry 干掉(具体源码见 ThreadLocalMap 解析)
// --------------------ThreadLocal.remove()-------------------------------
	public void remove() {
    	// 获取当前线程内部的ThreadLocalMap对象
        ThreadLocalMap m = getMap(Thread.currentThread());
        // m不为null,说明说当前线程已经初始化过 threadLocalMap对象了
        if (m != null)
            // 调用remove方法(key = 当前threadLocal对象)
            // ThreadLocalMap.remove()方法 在下一篇ThreadLocalMap内核分析
            m.remove(this);
	}

小结

  • 我们只分析了 ThreadLocal 表层的 get()、set()、remove() 方法,表层的方法实现非常简单,因为复杂的逻辑都在 ThreadLocalMap 内核中。
  • 比如 ThreadLocalMap 中的 getEntry()、set()、remove() 等方法,这些方法才是 ThreadLocal 的真正实现,在下一篇文章分析。

参考

  • 视频参考
    • b站_小刘讲源码付费课
  • 文章参考
    • shstart7_ThreadLocal源码解析1.运行原理
    • 兴趣使然的草帽路飞_ThreadLocal源码分析_01 入门案例以及表层源码分析
    • 肆华_ThreadLocal阅读理解

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/43973.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Mybatis编程:根据若干个id批量删除相册(动态SQL)】

目录 1. 执行的SQL语句 2. 在AlbumMapper.java接口添加抽象方法 3. 在AlbumMapper.xml中配置以上抽象方法映射的SQL语句 4. 标签书写规范 1. 执行的SQL语句 需要执行的SQL语句大致是&#xff1a; delete from pms_album where id? or id? or ... id? delete from pms…

《机器学习实战》10.K-均值聚类算法

目录 利用K-均值聚类算法对未标注数据分组 K-均值聚类算法 2 使用后处理来提高聚类性能 3 二分K-均值算法 4 示例&#xff1a;对地图上的点进行聚类 4.1 Yahoo&#xff01;PlaceFinder API 4.2 对地理坐标进行聚类 5 本章小结 本章涉及到的相关代码和数据 利用K-均值聚…

Unity基本编译环境设置(代码自动补全)

基本说明&#xff1a; 中国 Unity 官网下载地址&#xff1a;https://unity.cn/releases 请下载 Unity HUB 来管理和安装你的 Unity 各种版本 场景一&#xff1a; Windows系统 &#xff5c;Unity 2020之前的版本 &#xff5c;Visual Studio Community编辑器 电脑中没有任何…

并发控制常用定位方法及解决措施

并发控制常用定位方法及解决措施 7.1 排队问题 出现业务阻塞、性能下降、查询无响应等类似现网问题时&#xff0c;通过以下方法可以排查是否排队问题并定位排队原因&#xff0c;同时根据排队原因给出相应规避措施。 7.1.1 确认是否排队 首先确认是否排队问题&#xff0c;其…

力扣(LeetCode)2095. 删除链表的中间节点(C++)

快慢指针 设置哑结点&#xff0c;便于删除头结点。找到链表的中间结点&#xff0c;可以用快慢指针从头结点出发&#xff0c;慢指针最后停在中间结点。删除中间结点&#xff0c;应当找中间结点的前一个结点。于是想到加入哑结点&#xff0c;这样初始快慢指针既可以往前一个位置…

RADServer应用程序的交钥匙应用程序基础

RADServer应用程序的交钥匙应用程序基础 RADServer是快速构建和部署基于服务的应用程序的交钥匙应用程序基础。RAD Server提供自动化的Delphi和CREST/JSON API发布和管理、企业数据库集成中间件、IoT Edgeware和一系列应用程序服务&#xff0c;如用户目录和身份验证服务、推送通…

retimer芯片调式总结

1,主备状态是否ok。 看里面的0-3工作在Master(master Active), 4-7 工作在Master(wait master),主控0-3,备控4-7. current postion说明: typedef enum {VEGA_HMUX_SWITCH_TO_MASTER = 0,VEGA_HMUX_SWITCH_TO_SLAVE = 1,VEGA_HMUX_SWITCH_TO_PATTERN = 2,VEGA_HMUX_SWITC…

VSCode配置ssh连接本地wsl方法

1、首先需要安装插件Remote-SSH 2、其次在wsl子系统中安装ssh服务并设置允许密码登入 3、开启ssh服务并查看wsl子系统的IP地址 4、返回vscode里面的Remote SSH插件&#xff0c;点击下图中的那个号进行设置&#xff1a; 首先输入的是连接名字 &#xff0c;按enter键确认之后选…

施工企业数字化转型如何避免IT技术与企业管理的“两张皮”

工程项目是建筑产业的最基本单元&#xff0c;企业的生产经营数据都来源于项目&#xff0c;通过量化建造过程中的生产、管理要素&#xff0c;利用IoT、BIM、大数据、AI等核心技术&#xff0c;实时采集现场真实、唯一、精准、有效的工程项目资金、成本、进度、质量、安全、技术等…

NET:Spire.XLS 12.11.3 supports a variety of new formulas

Spire.XLS 12.11.3 supports a variety of new formulas 发现度娘破解版Spire.XLS for .NET is a professional Excel .NET API that can be used to create, read, write, convert and print Excel files in any type of .NET ( C#, VB.NET, ASP.NET, .NET Core, .NET 5.0, .…

【纯虚函数】设计一个形状类——矩形、圆形、三角形,分别计算三种当前图形的周长和面积

目录 一、纯虚函数 抽象类 纯虚函数概念&#xff1a; 抽象类的概念&#xff1a; 二、习题 题目&#xff1a; 代码&#xff1a; 测试结果&#xff1a; 一、纯虚函数 抽象类 纯虚函数概念&#xff1a; 是指没有具体实现的虚成员函数。用于这样的情况&#xff1a;设计一个…

QT+Python人脸表情特征识别

程序示例精选 QTPython人脸表情特征识别 前言 QTPython是非常经典的窗体编程组合&#xff0c;功能完善&#xff0c;可视化界面美观易维护&#xff0c;这篇博客针对人脸表情特征识别方面编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读&#xff0c;对学习与使用P…

docker安装及优化

一、docker安装步骤详解 docker初期版本是1.13&#xff08;同一版本&#xff0c;开源&#xff09; ——》分类型 1.15 - 1.17 过程中分成两种。 ①开源社区 docker-ce ②企业版 docker-ee <span style"color:#000000"><span style"background-colo…

《web结课作业的源码》中华传统文化题材网页设计主题——基于HTML+CSS+JavaScript精美自适应绿色茶叶公司(12页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

石化技术杂志石化技术杂志社石化技术编辑部2022年第10期目录

工业、生产《石化技术》投稿&#xff1a;cnqikantg126.com 衰减全反射傅里叶变换红外光谱测定溴化丁基橡胶中硬脂酸钙含量 俞培富;王晗;李振;邓洁;宋轶;姜旭鞠; 1-292 聚丙烯催化剂均聚性能实验研究 关健;付玉祥; 3-5 无损检测在石油钻具失效检验中的应用 李梅英;吕…

pandas中read_csv和to_csv、read_hdf和to_hdf、read_json和to_json函数及其他各类文件的读取与存储

pandas I/O API 是一组访问的顶级读取器函数&#xff0c;如 pandas.read_csv&#xff08;&#xff09;&#xff0c;通常返回一个 pandas 对象。相应的编写器函数是访问的对象方法&#xff0c;如 DataFrame.to_csv&#xff08;&#xff09; 一、read_csv和to_csv pandas.read_…

适用场景全新升级!扩展 Dragonfly2 作为分布式缓存系统架构 | 龙蜥技术

文/龙蜥社区开发者 Dragonfly2 简介 Dragonfly 作为龙蜥社区的镜像加速标准解决方案&#xff0c;是一款基于 P2P 的智能镜像和文件分发工具。它旨在提高大规模文件传输的效率和速率&#xff0c;最大限度地利用网络带宽。在应用分发、缓存分发、日志分发和镜像分发等领域被大规…

1538_AURIX_TriCore内核架构_地址映射以及存储配置

全部学习汇总&#xff1a; GreyZhang/g_tricore_architecture: some learning note about tricore architecture. (github.com) 继续看TriCore的内核手册&#xff0c;这一次看一下地址映射以及存储配置。我大概顺了一遍之后&#xff0c;感觉这部分还是很简单的。而看完这部分&a…

Java使用DOM简单解析XML文件

Java使用DOM简单解析XML文件前言本文目标目标结果演示示例文件信息测试结果解析XML文件相关的Java类DocumentBuilderFactoryDocumentBuilderDocumentNodeListNode简单流程分析练练手NodeList及Node的部分结构分析关于#Text工具类测试测试代码及说明输出结果小结前言 对于某些需…

Python写了个疫情信息快速查看工具

年关将至&#xff0c;大家对疫情的关注度也愈发提升&#xff0c;本次使用PyQt5撰写100行代码写一个疫情信息快速查看工具。 一&#xff0e;准备工作 1.PyQt5 PyQt 是一个用于创建GUI应用程序的跨平台的工具包&#xff0c;它将Python编程语言和Qt库 成功融合在一起。QT库目前…