ThreadLocal初探

news2024/11/24 9:23:07

一、ThreadLocal介绍

一、官方介绍

从Java官方文档中的描述:ThreadLocal类用来提供线程内部的局部变量,这种变量在多线程环境下访问(通过get和set方法访问)时,能够保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static 类型的,用来关联线程个线程上下文。

 二、ThreadLocal线程的作用

提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量的复杂度。

总结:

1、线程并发:ThreadLocal一般是在多线程并发的场景下使用,不是多线程的环境下是没必要使用的。

2、传递数据:在同一个线程,不同的组件进行公共变量的传递可以使用ThreadLocal

3、线程隔离:每一个线程中的变量都是独立的,线程之间的变量互不影响。

三、常用方法

四、 ThreadLocal与Synchronized之间的区别

虽然两者都是用于处理多线程并发访问变量的问题,不过两者之间处理问题的角度和思路不同。

二、ThreadLocal的内部结构

一、ThreadLocal常见误解

ThreadLocal早期设计:每一个ThreadLocal都会创建一个map,然后用线程作为map的key,要存储的局部变量作为map的value,这样就能达到各个线程的局部变量隔离的效果。

二、 ThreadLocal当前设计

JDK后面优化了设计方案,在jdk8中ThreadLocal设计是:每个Thread维护一个ThreadLocalMap,这个map的key是ThreadLocal实例本身,value才是真正要存储的值Object。

1、每个Thread线程内部有一个Map(ThreadLocalMap)

2、每个Map中存储ThreadLocal变量(key)和线程的变量副本(value)

3、Thread内部内部的map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

4、对于不同线程,每次获取副本值时,别的线程并不能获取当前线程的副本值,形成了副本的隔离,互不干扰。

三、两者对比

 三、ThreadLocal源码分析

ThreadLocal对外暴露的方法有以下几个:

 一、Set方法

1、首先获取当前线程,并根据当前线程获取一个Map集合

2、如果获取的集合不为空,则将参数设置到map中,其中ThreadLocal作为key

3、如果为空,则给改线程创建一个新的map,并设置初始值。

 二、get方法

/**
 返回该线程本地变量当前线程的副本中的值。
 如果变量没有当前线程的值,则首先将其初始化为调用 {@ link initialValue} 方法返回的值。
 返回该线程的当前线程值-local
 */
public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程维护的ThreadLocalMap 
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 以当前ThreadLocal为key,获取对应的存储实体。this表示当前对象ThreadLocal
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
             // 这里主要是想获取当前线程,对应的ThreadLocal的值
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    /*
     初始化:有两种情况下会执行
        1、map不存在时会执行,表示当前线程没有维护ThreadLocalMap 
        2、map存在,但是没有与当前ThreadLocal关联的值
     */
    return setInitialValue();
}

 

三、remove方法

 四、initialValue方法

 五、ThreadLocalMap源码分析

一、基本结构

ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也是独立实现的。

二、ThreadLocalMap中的成员变量 

 三、存储结构--Entry

 四、内存泄漏与弱引用

(1)内存泄漏相关概念:

1、Memery overflow:内存溢出,没有足够的内存给申请者使用。

2、Memery leak:内存泄漏,程序中已动态分配的堆内存,由于某种原因程序未能释放或无法释放,造成系统内存的浪费,导致程序运行减慢或者系统崩溃等严重后果。内存泄漏的堆积终将导致内存溢出。

(2)弱引用相关概念

java中的引用有四种类型:强、软、弱、虚。

1、强引用(Strong Reference):就是我们最常见的普通对象的应用,只要还有强应用指向一个对象,就表明对象还活着,垃圾收集器就不会回收这个对象的内存。

2、软引用(Soft Reference ):

如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;

如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以被程序使用。

3、弱引用(Weak Reference ):垃圾收集器一旦发现有弱引用的对象,不管当前内存是否足够,都会立即回收他的内存。

4、虚引用(Phantom Reference):

虚引用和前面的软引用、弱引用不同,它并不影响对象的生命周期。在java中用java.lang.ref.PhantomReference类表示。如果一个对象与虚引用关联,则跟没有引用与之关联一样,在任何时候都可能被垃圾回收器回收。

要注意的是,虚引用必须和引用队列关联使用,当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会把这个虚引用加入到与之 关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

之后会另起一篇记录

 (3)如果key是强引用

假设ThreadLocalMap中的key使用了强引用, 那么会出现内存泄漏吗?

1、假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了
2、但是因为threadLocalMap的Entry强引用了threadLocal, 造成ThreadLocal无法被回收
3、在没有手动删除Entry以及CurrentThread依然运行的前提下, 始终有强引用链threadRef → currentThread → entry, Entry就不会被回收( Entry中包括了ThreadLocal实例和value), 导致Entry内存泄漏

也就是说: ThreadLocalMap中的key使用了强引用, 是无法完全避免内存泄漏的
 

(4)如果key是弱引用 

1、假设在业务代码中使用完ThreadLocal, ThreadLocal ref被回收了

2、由于threadLocalMap只持有ThreadLocal的弱引用, 没有任何强引用指向threadlocal实例, 所以threadlocal就可以顺利被gc回收, 此时Entry中的key = null

3、在没有手动删除Entry以及CurrentThread依然运行的前提下, 也存在有强引用链threadRef → currentThread → value, value就不会被回收, 而这块value永远不会被访问到了, 导致value内存泄漏

也就是说: ThreadLocalMap中的key使用了弱引用, 也有可能内存泄漏。
 

(5)内存泄漏的真实原因 

出现内存泄漏的真实原因出改以上两种情况,
比较以上两种情况,我们就会发现:
内存泄漏的发生跟 ThreadLocalIMap 中的 key 是否使用弱引用是没有关系的。那么内存泄漏的的真正原因是什么呢?

在以上两种内存泄漏的情况中.都有两个前提:
1 、没有手动删除这个 Entry
2 ·、CurrentThread 依然运行
第一点很好理解,只要在使用完下 ThreadLocal ,调用其 remove 方法翻除对应的 Entry ,就能避免内存泄漏。
第二点,由于ThreodLocalMap 是 Thread 的一个属性,被当前线程所引所以它的生命周期跟 Thread 一样长。那么在使用完 ThreadLocal 的使用,如果当前Thread 也随之执行结束, ThreadLocalMap 自然也会被 gc 回收,从根源上避免了内存泄漏。

综上, ThreadLocal 内存泄漏的根源是:

由于ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存泄漏.
 

(6)为什么使用弱引用 

无论 ThreadLocalMap 中的 key 使用哪种类型引用都无法完全避免内存泄漏,跟使用弱引用没有关系。

​ 要避免内存泄漏有两种方式:
​ 1 .使用完 ThreadLocal ,调用其 remove 方法删除对应的 Entry
​ 2 .使用完 ThreadLocal ,当前 Thread 也随之运行结束

​ 相对第一种方式,第二种方式显然更不好控制,特别是使用线程池的时候,线程结束是不会销毁的.

​ 也就是说,只要记得在使用完ThreadLocal 及时的调用 remove ,无论 key 是强引用还是弱引用都不会有问题.
 

那么为什么 key 要用弱引用呢

​ 事实上,在 ThreadLocalMap 中的set/getEntry 方法中,会对 key 为 null (也即是 ThreadLocal 为 null )进行判断,如果为 null 的话,那么是会又如 value 置为 null 的.

​ 这就意味着使用完 ThreadLocal , CurrentThread 依然运行的前提下.就算忘记调用 remove 方法,弱引用比强引用可以多一层保障:弱引用的 ThreadLocal 会被回收.对应value在下一次 ThreadLocaIMap 调用 set/get/remove 中的任一方法的时候会被清除,从而避免内存泄漏.

五、Hash冲突解决

Hash冲突的解决是Map中的一个重要内容。

1、构造方法ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)

/**
 * firstKey:ThreadLocal实例
 * firstValue:要保存的线程本地变量
 */
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    // 为Entry数组设置初始化值16
    table = new Entry[INITIAL_CAPACITY];
    // 计算索引
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    // 设置值
    table[i] = new Entry(firstKey, firstValue);
    // 数组存储的个数
    size = 1;
    // 设置阈值
    setThreshold(INITIAL_CAPACITY);
}

构造函数首先创建一个长度为16的数组,然后计算出firstKey对应的索引,然后存储到table

中,并设置size和threshould

主要分析: int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

1、firstKey.threadLocalHashCode

private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}
private static AtomicInteger nextHashCode =
    new AtomicInteger()
private static final int HASH_INCREMENT = 0x61c88647;

 &(INITIAL_CAPACITY - 1)

2、ThreadLocalMap中的Set方法

private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    // 计算索引,相当于对数组长度取余
    int i = key.threadLocalHashCode & (len-1);

    /**
        使用线性探测法查找元素
        Entry e = tab[i]:取出i这个位置的Entry 
        e != null:对取出的Entry 进行判空,不为空时继续循环
        e = tab[i = nextIndex(i, len)]:长度加一
    */
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        // ThreadLocal对应的key存在,则直接覆盖之前的值
        if (k == key) {
            e.value = value;
            return;
        }
        // key为null,但是值不为null,说明之前的ThreadLocal对象已经被回收了,当前       
        // Entry是一个陈旧元素
        if (k == null) {
                // 用新元素替换旧元素,并且进行垃圾清理,防止内存泄漏
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    /**
        ThreadLocal对应的key不存在,且没有找到陈旧元素,则在空元素的位置创建一个新的             Entry
     */   
    tab[i] = new Entry(key, value);
    int sz = ++size;
     
     /**
        cleanSomeSlots用于清除e.get()==null的元素
        这种数据key关联的对象已经被回收了,如果没有
        清理任何的Entry,并且当前数量达到了负载因子所定义(长度的三分之二)
        那么进行rehash();执行一次全表的扫描清理工作
     **/   
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

// 获取环形数组的下一个索引

private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

 

 

 

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

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

相关文章

apple pencil必须要买吗?性价比平替电容笔排行榜

要知道&#xff0c;真正的苹果原装Pencil&#xff0c;价格实在是太贵了&#xff0c;普通的消费者根本买不起。所以&#xff0c;有没有可能出现一种平替的、功能一模一样的、与苹果Pencil一样的电容笔呢&#xff1f;这倒也是。国产的平替笔和苹果Pencil的笔相比&#xff0c;并没…

Wireless-Sensor-Network-master_WSN_无线传感网络(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f468;‍&#x1f4bb;4 Matlab代码 &#x1f4a5;1 概述 近年来&#xff0c;随着对等网络、云计算和网格计算等分布式环境的发展&#xff0c;无线传感器网络&#xff08;WSN&#xff0…

10分钟吃透,python操作mysql数据库的增、删、改、查

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…

聊聊汽车OTA测试技术方案

汽车OTA已成为时下热门话题&#xff0c;由于OTA的升级可能会带来一定的风险&#xff0c;针对OTA的测试就尤为重要。本文我们主要通过介绍OTA的发展背景、汽车OTA测试的必要性以及汽车OTA测试内容&#xff0c;为大家分享一套成熟的OTA测试方案。 什么是OTA OTA&#xff08;Over-…

以最大速度将数据迁移至AWS S3存储

数据上云&#xff0c;已经成为企业数据管理发展的必然趋势。 对于企业而言&#xff0c;数据上云“常态化”的趋势&#xff0c;无疑是一个巨大的技术红利。而数据规模爆发式增长的今天&#xff0c;移动和访问云端数据却成为困扰企业的一大难题。不过幸运的是&#xff0c;在对象…

ES6:promise简单学习

一、理解promise Promise将异步执行的程序变成同步执行&#xff0c;所谓的在开发中解决回调嵌套的问题 Promise 是异步编程的一种解决方案 从语法上讲&#xff0c;promise是一个对象 从它可以获取异步操作的消息 从本意上讲&#xff0c;它是承诺&#xff0c;承诺它过一段时间会…

Java入门超详细(内含Java基础 Java高级)

Java基础入门 - 内含Java基础&#xff0c;Java高级 Java 基本介绍Java 学习须知Java 学习文档Java 基础Java 基础语法Java 对象与类Java 基本数据类型Java 变量类型Java 修饰符Java 运算符Java 循环结构Java 条件语句Java switch caseJava 数组Java 日期与时间Java 正则表达式J…

Swift AsyncSequence — 代码实例详解

前言 AsyncSequence 是并发性框架和 SE-298 提案的一部分。它的名字意味着它是一个提供异步、顺序和迭代访问其元素的类型。换句话说&#xff1a;它是我们在 Swift 中熟悉的常规序列的一个异步变体。 就像你不会经常创建你的自定义序列一样&#xff0c;我不期望你经常创建一个…

2022 Jiangsu Collegiate Programming Contest A. PENTA KILL!

题目链接 Sample 1 Input 10 Bin Guigo Grevthar Bin GALA Grevthar GALA TitaN GALA Guigo GALA Aegis GALA Jojo GALA Grevthar Xiaohu Grevthar GALA Aegis Output PENTA KILL! Sample 3 Input 7 GALA Jojo Aegis Ming GALA Grevthar GALA Grevthar GALA Aegis GALA Guigo…

树莓派硬件介绍及配件选择

目录 树莓派Datasheet下载地址&#xff1a; Raspberry 4B 外观图&#xff1a; 技术规格书&#xff1a; 性能介绍&#xff1a; 树莓派配件选用 电源的选用&#xff1a; 树莓派外壳选用&#xff1a; 内存卡/U盘选用 树莓派Datasheet下载地址&#xff1a; Raspberry Pi …

CompletableFutrue异步处理

异步处理 一、线程的实现方式 1. 线程的实现方式 1.1 继承Thread class ThreadDemo01 extends Thread{Overridepublic void run() {System.out.println("当前线程:" Thread.currentThread().getName());} }1.2 实现Runnable接口 class ThreadDemo02 implements …

故障:更新后电脑卡顿

前一天下班的时候关电脑&#xff0c;关机选项变成了“更新并关机”&#xff0c;没多想&#xff0c;我点了。。。。早上上班就发现电脑卡得不行&#xff0c;看了下更新日志&#xff0c;装了一大堆东西&#xff0c;看了下任务管理器&#xff0c;内存直接跑到了90%&#xff0c;这电…

每日学术速递5.2

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.DataComp: In search of the next generation of multimodal datasets 标题&#xff1a;DataComp&#xff1a;寻找下一代多模态数据集 作者&#xff1a;Samir Yitzhak Gadre, Gab…

【JAVA模块六------ 综合案例基础巩固】

JAVA模块六------ 综合案例基础巩固 1 逢7跳过2 数组元素求和3 判断两个数组的内容是否相同4 查找某个数组元素索引5 数组元素反转输出&#xff1a;&#xff08;逆序输出&#xff09;6 评委打分7 随机产生验证码其他&#xff1a;方法抽取&#xff1a; 1 逢7跳过 要求&#xff1…

国产光伏仪器 6581太阳能电池板伏安特性测试仪

6581太阳能电池板伏安特性测试仪主要用于太阳能电池板生产的最终测试&#xff0c;也可以作为层压前测试使用&#xff0c;能大大提高一次封装成品率。该测试仪适合于单晶、多晶、薄膜等多种电池组件&#xff0c;可进行I-V曲线、P-V曲线、短路电流、开路电压、峰值功率等全部参数…

Hive SQL on Flink 构建流批一体引擎

摘要&#xff1a;本文整理自阿里巴巴开发工程师罗宇侠、阿里巴巴开发工程师方盛凯&#xff0c;在 Flink Forward Asia 2022 流批一体专场的分享。本篇内容主要分为五个部分&#xff1a; 1. 构建流批一体引擎的挑战 2. Hive SQL on Flink 3. 流批一体引擎的收益 4. Demo 5. 未来…

做到“有效沟通”,帮你达到这3个目的

在项目管理中&#xff0c;团队沟通是至关重要的。团队成员之间应该建立良好的沟通机制&#xff0c;及时沟通和协调问题&#xff0c;避免出现问题后甩锅的情况。 在实际项目中&#xff0c;很多问题出现的原因是团队沟通不畅&#xff0c;项目经理需要加强团队沟通的重要性&…

K8S二进制安装报错及各个组件功能介绍

目录 一、K8S安装二、安装时遇到的几个问题2.1、Unable to connect to the server: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kuberne…

C语言复习笔记2

1.变量命名只能以数字、字母、下划线组成并且不能以数字开头。 #include<stdio.h> #include<unistd.h>//变量名只能由数字字母下划线组成&#xff0c;不能以数字开头 int main() {//int 2b;return 0; }2.内存中保存的是补码 0的补码取反得补码再求源码是-1。 源码…

提升论文影响力的方法

论文发表后&#xff0c;还有一些重要的工作去做&#xff0c;那就是去积极宣传和推广自己的论文&#xff0c;提高自己论文的影响力。这类似于一个电影上映后&#xff0c;主演们还得去做宣传一样&#xff0c;要想办法推销自己的作品。本文将介绍提升论文影响力的方法。 1. 开源数…