ThreadLocal原理和实践

news2024/11/25 10:48:18

一、概述

ThreadLocal是线程本地变量,解决多线程环境下成员变量共享存在的问题。ThreadLocal为每个线程创建独立的的变量副本,他的特性是该变量的引用对全局可见,但是其值只对当前线程可用,每个线程都将自己的值保存到这个变量中而各线程不受影响。

二、原理

1.原理说明

ThreadLocal的大致原理是这样的:

  1. 每个线程里面就有一个ThreadLocalMap的对象引用,ThreadLocalMap对象的数据结构是Entry组成的Map,其key值是ThreadLocal,而value值才是存放在该线程需要保存的value;
  2. 调用ThreadLocal的set()方法就往ThreadLocalMap里里面存值,value就是存入的值;
  3. 调用ThreadLocal的get()方法就是从ThreadLocalMap里面根据key值来取value;

ThreadLocal在JVM详细的逻辑图见下:

4A08ACC1-33F5-4834-83A1-A24ADDD35326

我的理解是ThreadLocal其实是为每一个线程创建一个副本变量,对于每个线程来说其变量引用相同,但其值不同。最简单的思路是创建一个Map,其key值是线程id,其值是各个线程的副本value,但是由于已经明确变量引用相同,所以该Map的key即为ThreadLocal本身,将其key和Value组合成一个实体Entry,放到其特定的ThreadLocalMap中,这就是ThreadLocal的原理。简而言之就是存放副本的数据结构ThreadLocalMap存在双头领导,一头已经释放,但另一头由于存活时间很长,所以很难释放对ThreadLocalMap的引用,从而导致ThreadLocalMap无法被及时GC。

2.代码分析

Thread中的成员变量threadLocals:

//Thread.java

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap的数据结构:

//ThreadLocal.ThreadLocalMap

static class ThreadLocalMap {
//实际保存数据的数据结构为Entry,其key为ThreadLocal的引用,value为各个线程需要保存的值
    static class Entry extends WeakReference> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

//成员变量
    /**
     * The initial capacity -- MUST be a power of two.
     */
    private static final int INITIAL_CAPACITY = 16;

    /**
     * The table, resized as necessary.
     * table.length MUST always be a power of two.
     */
    private Entry[] table;

    /**
     * The number of entries in the table.
     */
    private int size = 0;

    /**
     * The next size value at which to resize.
     */
    private int threshold; // Default to 0

    /**

    //省略...
    }

总的来说,ThreadLocalMap是保存多线程多副本的数据结构,Thread会保存ThreadLocalMap的引用,ThreadLocal也会保存ThreadLocalMap的引用。

三、实践

1.常用方法

ThreadLocal种最常见的呃是set()和get()方法,下面讲解下这两个方法;

  • set()方法

set()方法,顾名思义就是往ThreadLocal种塞值的,每个线程塞的值只对自己的线程可见;

  • get()方法

get()方法和set()方法对应,从ThreadLocal种取值出来;

  • initialValue()方法

设置ThreadLocal中变量的默认值;

  • remove()

移除该线程在ThreadLocal中的变量副本;

2.使用场景

ThreadLocal的使用场景包括多个线程要调用同一个资源,但该资源内部要为每一个线程分配不同资源的情况,比如数据库连接,在建立数据库连接时候,只需要建立一个datasource,通过这一个datasource可以为不同的请求建立connection连接;

3.代码实践

创建多个线程,分别验证通过set()方法设置值和get()获取值,并打印给ThreadLocal设置的默认值。

public class ThreadLocalTest {
    public static void main(String[] args) {
        ThreadLocal threadLocal = new ThreadLocal(){
            @Override
            protected Integer initialValue() {
                return 0;
            }
        };

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(1);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set(2);
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

四、常见问题

1.碰到get()方法返回null的处理方法?

可以通过extends ThreadLocal并且重写initialValue()来实现初始化;

2.ThreadLocal内存泄露的解决方法?

从上面的逻辑图可以看到,ThreadLocalMap是在Thread里面的,所以ThreadLocalMap和Thread的生命周期是一样长的,但是ThreadLocalMap又被ThreadLocal引用,即使ThreadLocal已经使用完,但由于Thread没有将其引用释放,所以ThreadLocalMap还是不会被GC掉,这样很容易导致OOM,处理方法是线程在不用ThreadLocal的时候记得remove掉;


参考资料

  1. ThreadLocal就是这么简单:https://juejin.cn/post/6844903586984361992
  2. Java 并发 - ThreadLocal详解:https://pdai.tech/md/java/thread/java-thread-x-threadlocal.html
  3. 对ThreadLocal实现原理的一点思考:https://www.jianshu.com/p/ee8c9dccc953
  4. Java多线程编程-(8)-多图深入分析ThreadLocal原理:https://blog.csdn.net/xlgen157387/article/details/78297568
  5. 《Java多线程编程核心技术》
  6. 《码出高效java代码》

    本文由博客一文多发平台 OpenWrite 发布!

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

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

相关文章

Java并发编程(二)并发理论[JMM/重排序/内存屏障/Happens-Before 规则]

JMM(Java内存模型) 概述 JMM即Java内存模型(Java Memory Model),是一种抽象的概念,并不真实存在,JMM描述的是一组规则或规范,通过这组规范定义了程序中各个变量的访问方式Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操…

Java实现籍贯级联选择器

在工作中要求写一个籍贯的级联选择器,记录一下自己写这个级联选择器的过程,因为自己才刚开始工作,有很多地方都没有考虑的很清楚,希望各位大佬能给出建议。 一、需求 A:正常的23个省,籍贯由“省区/县/市”组成&#xf…

javaScript:分支语句的理解与使用(附带案例)

目录 前言 补充 另一种说法 分支语句 1.if语句 a.单分支语句 注意 b.双分支语句 注意点 c.多分支语句(分支语句的联级语句) 补充 2.三元运算符 三元运算符 ? : 使用场景 3.switch语句 解释 释义&#xff1a…

2000-2022年全国地级市乡村振兴测算数据(30个指标)

1、时间:2000-2022年 2、来源:城市统计NJ、各地区统计NJ 3、范围:地级市 4、指标:乡村振兴指数、人均农业机械总动力(千瓦)、粮食综合生产能力(万吨)、农业劳动生产率&#xff08…

ToolLLM:能够使用 16000 种真实 API 的 LLM

随着对 LLM(大语言模型)的了解与使用越来越多,大家开始偏向于探索 LLM 的 agent 能力。尤其是让 LLM 学习理解 API,使用工具进行对用户的 instruction(指令)进行处理。然而,目前的开源大模型并不…

Oracle连接数据库提示 ORA-12638:身份证明检索失败

ORA-12638 是一个 Oracle 数据库的错误代码,它表示身份验证(认证)检索失败。这通常与数据库连接相关,可能由于以下几个原因之一引起: 错误的用户名或密码: 提供的数据库用户名或密码不正确,导致…

开源数据库Mysql_DBA运维实战 (DDL语句)

DDL DDL语句 数据库定义语言:数据库、表、视图、索引、存储过程. 例如:CREATE DROP ALTER DDL库 定义库{ 创建业务数据库:CREAATE DATABASE ___数据库名___ ; 数据库名要求{ a.区分大小写 b.唯一性 c.不能使用关键字如 create select d.不能单独使用…

单片机学到什么程度可以找到工作?

STM32是意法半导体公司推出一款32位的单片机,其具有超低的价格、超多的外设、丰富的型号、优异的实时性、极低的开发成本等优势,并且stm32相关资料都非常全面和细致,所以很适合小白的学习。对于开发方式的选择,选择一种适合自己的…

MySQL索引3——Explain关键字和索引使用规则(SQL提示、索引失效、最左前缀法则)

目录 Explain关键字 索引性能分析 Id ——select的查询序列号 Select_type——select查询的类型 Table——表名称 Type——select的连接类型 Possible_key ——显示可能应用在这张表的索引 Key——实际用到的索引 Key_len——实际索引使用到的字节数 Ref ——索引命…

day1 ARM架构概述

ARM处理器架构 1、指令集: 1.1、复杂指令集(CISC):包含处理复杂操作的特定指令,指令长度不固定,执行需要多个周期; 1.2、简单指令集(RISC):指令简单而有效,格式和长度通常是固定的,…

RISC-V公测平台发布 · 使用YCSB测试SG2042上的MySQL性能

实验介绍: YCSB(全称为Yahoo! Cloud Serving Benchmark),该性能测试工具由Java语言编写(在之前的MC文章中也提到过这个,如果没看过的读者可以去看看之前MC那一期),主要用于云端或者…

WebSocket整合spring 一文全部搞定

文章声明 本文简单整合了webSocket 组件,涉及到的源码分解,原理什么的以后再说,本文只适合入门小白体验,不涉及复杂业务逻辑。 文章目录 1 引入webSocket依赖包2 声明式整合WebSocket(这是一道硬菜)2.1 webSocket 配置类2.2 webs…

Java 数据库时间返回前端显示错误(差8个小时)

文章目录 JsonFormat 与 DateTimeFormat 使用0 可能错误截图1 在属性上加自定义Json返回注释 JsonSerialize2 新建实体类 CustomDateTimeSerializer3 前端传后端格式转换(ISO 日期格式)转(Data)4 一个注释解决双端转化问题 JsonFormat 与 Dat…

Java 本地缓存之王:Caffeine 保姆级教程

一、Caffeine介绍 1、缓存介绍 缓存(Cache)在代码世界中无处不在。从底层的CPU多级缓存,到客户端的页面缓存,处处都存在着缓存的身影。缓存从本质上来说,是一种空间换时间的手段,通过对数据进行一定的空间安排,使得下…

matplotlib 为图顶部和图右部的坐标轴添加标记label

Matplotlib 中,默认情况下,只有底部和左侧的坐标轴有标记 1 设置底部坐标轴标签 通过使用ax.xaxis.set_label_position() 调整标签的位置 import matplotlib.pyplot as plt# 创建一个图表 fig, ax plt.subplots()# 生成示例数据 x [1, 2, 3, 4, 5] …

激光焊接塑料多点测试全画面穿透率测试仪

工程塑料由于其具有高比强度、电绝缘性、耐磨性、耐腐蚀性等优点,已广泛应用于各个重要领域。另一方面,工程塑料还具有良好的焊接性,是制成复合材料的基体材料的优良选择,因此目前已成为国内外新型复合材料的研究热点。 工程塑料…

网络安全 Day27-运维安全项目-iptables防火墙

iptables防火墙 1. 防火墙概述2. 防火墙2.1 防火墙种类及使用说明2.2 必须熟悉的名词2.3 iptables 执行过程※※※※※2.4 表与链※※※※※2.4.1 简介2.4.2 每个表说明2.4.2.1 filter表 :star::star::star::star::star:2.4.2.2 nat表 2.5 环境准备及命令2.6 案例01&#xff1a…

c基础扫雷

和三子棋一样,主函数先设计游戏菜单界面,这里就不做展示了。 初始化棋盘 初级扫雷大小为9*9的棋盘,但排雷是周围一圈进行排雷(8格),而边界可能会越界。数组扩大了一圈,行和列都加了2,所以我们用一个11*11的数组来初始化…

【论文阅读】基于深度学习的时序预测——Informer

系列文章链接 论文一:2020 Informer:长序列数据预测 论文二:2021 Autoformer:长序列数据预测 文章地址:https://arxiv.org/abs/2012.07436 github地址:https://github.com/zhouhaoyi/Informer2020 参考解读…

MySql之主从复制延时

MySql之主从复制延时 一、MySQL主从复制模型 一切都要从MySQL的主从复制模型开始说起,下图是最经典的MySQL主从复制模型架构图: 主从架构依赖于MySQL Binlog功能,Master节点上产生Binlog并将Binlog写入到Binlog文件中。 Slave节点上启动两…