ThreadLocal底层源码解析

news2024/11/17 17:27:33

线程隔离,保证多线性访问安全

每个线程拿到的值私有,相互不干扰
ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题

线程内的资源共享

1.多线程并发场景
2.通过ThreadLocal在同一个线程,不同组件中传递公共变量
3.每个线程变量独立,不会相互影响

使用场景

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。一个线程用自己的connection

基本方法

ThreadLoacl()创建对象
set设置当前线程绑定的局部变量
get得到当前线程绑定的局部变量
remove移除当前线程绑定的局部变量
在这里插入图片描述
有人说可以用synchronized,用这个效率低。只能一个个执行,会造成性能下降,并发下降

ThreadLocal和synchronized区别

synchronized时间换空间,ThreadLocal是空间换时间,每个线程提供变量副本,自己用自己的,每个线程相互隔离。

ThreadLocal中都会存在ThreadLoaclMap集合。集合里面存储的是key,value形式的数据,key代表的是threadlocal的一个成员对象,value对应的是当前线程里面的一个数据。
ThreadLocal是弱引用,允许被回收

ThreadLocal内部设计

在这里插入图片描述
这样设计好处是

  1. 每个Map存储的Entry数量变少,避免hash冲突
  2. Thread销毁时,ThreadLocalMap也会销毁,减少内存使用。

每个Thread都有个ThreadLocalMap,map的key是ThreadLocal本身,value才是真正存储的值object。即Map里面存储的是ThreadLocal对象和变量副本value

基于内部结构看源码

set方法

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
        //当前线程不存在ThreadLocalMap,开始初始化map对象,将当前线程和value作为第一个key放到map中
            createMap(t, value);
        }
    }
    //初始化
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //初始化
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//16
            table = new Entry[INITIAL_CAPACITY];
            //通过线程计算下标值
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            //new Entry,放入线程thread】和value
            table[i] = new Entry(firstKey, firstValue);
            //size修改为1,第一次初始化大小1
            size = 1;
            //threshold赋值,threshold = 16 * 2 / 3;初始容量的三分之二
            setThreshold(INITIAL_CAPACITY);
        }

获取当前线程维护的ThreadLocalMap

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

在这里插入图片描述

get方法

首先获取当前线程,根据当前线程获取一个Map
如果获取的Map不为空,则在map中以ThreadLocal的引用作为key来在map中获取对应的entry,
如果entry不为空,返回value
否则map为空或者entry为空,通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为key和value创建一个新的Map

public T get() {
        Thread t = Thread.currentThread();
        //获取当前t线程的map
        ThreadLocalMap map = getMap(t);
        //不为null
        if (map != null) {
        	//以当前ThreadLocal为key,得到entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //获取value
                T result = (T)e.value;
                return result;
            }
        }
        //初始化,map不存在,表示此线程没有维护的map对象,
        //map存在,但是没有当前ThreadLocal关联的entry
        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);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

remove方法,获取map,移除

protected T initialValue() {
        return null;
    }

ThreadLocalMap源码分析

map里面有Entry

static class ThreadLocalMap {
		//继承弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
			//key是ThreadLocal
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        }

在这里插入图片描述

弱引用和内存泄露

弱引用,垃圾回收器一旦发现了只具有弱引用的对象,不管当前内存空间是否足够,都会回收
内存泄露,指程序已经动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存浪费,导致程序运行减慢甚至崩溃,最终内存溢出。

如果key为null(弱引用,会回收)。
突然我们ThreadLocal是null了,也就是要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

为什么要使用弱引用

强弱都有可能有问题
在这里插入图片描述
只是多一层保障,彻底避免还是要使用完后remove

Hash冲突

初始化map时候

//通过线程计算下标值
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);

在这里插入图片描述
这个特殊的hash值和斐波那契数列有关,主要目的就是为了让hash码能均匀分布在2的n次方的数组里,也就是Entry[]中,这样做可以尽量避免hash冲突

hashcode&(size-1),hash发生冲突次数减少

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);
			//用线性探测法查找元素
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
				//覆盖
                if (k == key) {
                    e.value = value;
                    return;
                }
				//如果key是空,但是value不是,说明已经被回收,当前是个旧的无用的
                if (k == null) {
                //用新元素替换旧元素,垃圾清理,防止内存泄露
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            //cleanSomeSlots清除null元素
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
            //超过阈值,再hash
                rehash();
        }

扩容

在ThreadLocalMap.set()方法的最后,如果执行完启发式清理工作后,未清理到任何数据,且当前散列数组中Entry的数量已经达到了列表的扩容阈值(len*2/3),就开始执行rehash()逻辑:

private void rehash() {
            expungeStaleEntries();
通过判断size >= threshold - threshold / 4 也就是size >= threshold* 3/4 来决定是否扩容
            // Use lower threshold for doubling to avoid hysteresis
            if (size >= threshold - threshold / 4)
                resize();
        }

这里首先是会进行探测式清理工作,从table的起始位置往后清理。清理完成之后,table中可能有一些key为null的Entry数据被清理掉,所以此时通过判断size >= threshold - threshold / 4 也就是size >= threshold* 3/4 来决定是否扩容。我们还记得上面进行rehash()的阈值是size >= threshold,所以当面试官套路我们ThreadLocalMap扩容机制的时候 我们一定要说清楚这两个步骤

这里有扩容代码
扩容后的tab的大小为oldLen * 2,然后遍历老的散列表,重新计算hash位置,然后放到新的tab数组中,如果出现hash冲突则往后寻找最近的entry为null的槽位,遍历完成之后,oldTab中所有的entry数据都已经放入到新的tab中了。重新计算tab下次扩容的阈值。

private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }

在这里插入图片描述

如何正确的使用ThreadLocal

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

2、每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

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

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

相关文章

跳槽? 我只想多赚点罢了

前言 五一过后也就也就意味着今年的金三银四跳槽季正式结束了&#xff0c;不知道大家是否拿到了offer&#xff0c;面对金三银四的招聘旺季&#xff0c;如果没有精心准备那笔者认为那是对自己不负责任&#xff1b;就我们测试员来说&#xff0c;多数的公司总体上面试都是以自我介…

表的存储原理(数据库)

目录 一、内部存储概述 二、 数据行&#xff08;记录&#xff09;结构 1、定长记录 2、变长记录 一、内部存储概述 表是有关某个特定实例的数据集合&#xff0c;在关系数据库中处于核心地位。 创建一个表&#xff0c;就会有一行或多行插入到用来管理这个表的多个系统表里。…

如何压缩照片大小不大于200k

如何压缩照片大小不大于200k&#xff1f;有时候需要压缩照片大小不大于200k是因为在一些场景下&#xff0c;上传或传输大文件会受到限制&#xff0c;例如通过电子邮件发送、上传到云存储空间等等。在这种情况下&#xff0c;压缩照片可以让图片更容易地传输和分享&#xff0c;并…

基于simulink进行音频波束成形系统的多核仿真

一、前言 此示例展示了 Simulink中的音频波束成形系统仿真模型如何使用数据流域提高性能。它使用 Simulink 中的数据流域自动将通信系统的数据驱动部分划分为多个线程&#xff0c;从而通过在桌面的多个内核上执行模拟来提高仿真的性能。 二、介绍 数据流执行域允许您在计算密集…

Docker Overlay2占用大量磁盘空间解决

问题 最近项目的jenkins编译时报错 FATAL: Unable to produce a script filejava.io.IOException: No space left on deviceat java.io.UnixFileSystem.createFileExclusively(Native Method)at java.io.File.createTempFile(File.java:2024)at hudson.FilePath$CreateTextTem…

#vue项目冗余请求处理#http://localhost:8080/sockjs-node/info?t=1683604231866

目录 前言一、为什么会出现这个请求&#xff1f;二、解决方法1.修改源码 总结 前言 提示&#xff1a;本文要记录的大概内容&#xff1a; 在我的vue项目中&#xff0c;开发环境下&#xff0c;一直重复发请求&#xff1a;http://localhost:8080/sockjs-node/info?t16836042318…

《花雕学AI》33:如何用XMind制作AI思维导图、鱼骨图和组织结构图

思维导图是一种有效的思维工具&#xff0c;它可以帮助我们整理信息&#xff0c;激发创意&#xff0c;提高效率。思维导图是一种以中心主题为核心&#xff0c;以分支结构为形式&#xff0c;以关键词和图像为内容的图形表示法。它可以让我们一目了然地看到知识的层次和逻辑&#…

手把手教你Vue+ECharts+高德地图API实现天气预报数据可视化

前言 所谓数据可视化&#xff0c;我们可以理解为从宏观角度来看一眼就能看出来整个数据的占比&#xff0c;走向。对于数据可视化&#xff0c;很多互联网公司是很看重这一块的&#xff0c;包括大厂&#xff1b;就比如阿里的淘宝&#xff0c;双十一的时候往往就需要将消费者的一…

论文浅尝 | 句法丰富的判别训练:一种有效的开放信息抽取方法

笔记整理&#xff1a;杜苗增&#xff0c;东南大学硕士&#xff0c;研究方向为多模态信息抽取 链接&#xff1a;https://aclanthology.org/2022.emnlp-main.401.pdf 动机 开放信息提取(OIE)是信息提取(IE)的一个分支&#xff0c;专注于从非结构化自然语言文本中提取结构化信息。…

论文笔记_2017_RS_迈向高清 3D 城市测绘:基于道路特征的移动测绘系统和航空影像配准

基本情况 出处&#xff1a;Javanmardi M, Javanmardi E, Gu Y, et al. Towards high-definition 3D urban mapping: Road feature-based registration of mobile mapping systems and aerial imagery[J]. Remote Sensing, 2017, 9(10): 975.原文地址&#xff1a;Remote Sensin…

Postman生成代码的小技巧

描述 你还在使用postman吗&#xff1f;你还是一条条复制参数吗&#xff1f;你还是手动录入数据吗&#xff1f;对于一些不经常使用postman的人来说&#xff0c;这个小技巧可以帮助你导入请求&#xff0c;以及转换成开发语言。 教程 1 抓包接口 以CSDN热榜为例&#xff0c;直…

数据可视化二、综合项目

零、文章目录 数据可视化二、综合项目 1、项目概述 &#xff08;1&#xff09;项目展示 &#xff08;2&#xff09;项目目的 市场需求&#xff1a;应对现在数据可视化的趋势&#xff0c;越来越多企业需要在很多场景(营销数据&#xff0c;生产数据&#xff0c;用户数据)下使…

Fiddler 微信小程序抓图教程(傻瓜式|汉化版|狗看了都直呼内行)

前言 本篇文章主要给大家详细讲解如何用Fiddler爬取微信小程序的图片&#xff0c;内容图文并茂&#xff0c;流程非常简单&#xff0c;我们开始吧。 目录 获取软件并打开点击工具设置相关代理如何抓图答疑总结 一、获取软件并打开 1、通过百度网盘下载获取安装包(链接是永久的…

二十三种设计模式第六篇--建造者模式(也叫生成器)

建造者模式&#xff08;Builder Pattern&#xff09;是使用多个简单的对象一步一步&#xff08;顺序构建&#xff09;构建成一个复杂的对象&#xff0c; 这种类型的设计模式属于创建型模式&#xff0c;他提供了一种创建对象的最佳方式。 一个Builder类会一步一步构建成为最终的…

Windows10 WIFI蓝牙图标消失,网卡驱动出现感叹号等无法上网的情况解决方案

Windows10出现WIFI蓝牙图标消失&#xff0c;网卡驱动出现感叹号等无法上网的情况解决方案_飞机跑不快的博客-CSDN博客 问题描述 我的电脑是戴尔游匣G15 5511&#xff0c;由于静电保护的原因&#xff0c;不得不拆开电脑后盖拔掉电池&#xff0c;释放静电&#xff0c;释放完成后…

OpenPCDet系列 | 6.PointPillars模型分类、回归、角度损失的构建

文章目录 模型损失计算1. 分类损失构建1.1 分类损失函数:SigmoidFocalClassificationLoss2. 回归损失构建2.1 回归损失函数:WeightedSmoothL1Loss3. 角度损失构建3.1 角度损失函数:WeightedCrossEntropyLoss4. 总结模型损失计算 在进行anchor的正负样本分配后,具体来说就是…

【运动规划算法项目实战】专栏介绍

文章目录 前言1. 路径规划中常用的插值方法2. 路径规划中常用的抽稀3. 如何加载csv文件的路径信息4. 如何在栅格地图中实现A*算法5. 如何在栅格地图中实现Dijkstra算法6. 如何实现简单的状态机7. 如何实现机器人多目标点导航8. Voronoi图]9. 八叉树地图10. 如何实现三次样条插值…

网安笔记05 SHA

SHA Hash函数 定义 任意长度的数据M变换为定长码h h H A S H ( M ) h H ( M ) h HASH(M)\quad h H(M) hHASH(M)hH(M) 实用性&#xff1a; 给定M&#xff0c;计算h时高效的 安全性&#xff1a; 单向性 给出h&#xff0c;反向计算原文x时不可行的&#xff0c;否则截取…

vcomp140.dll怎么安装?提示vcomp140.dll丢失怎样修复?

在用电脑玩游戏或者打开软件工作的时候&#xff0c;电脑提示vcomp140.dll丢失无法执行此代码&#xff0c;是什么回事呢&#xff1f;需要怎么修复呢&#xff1f;不用紧张&#xff0c;小编今天就把vcomp140.dll文件修复方法分享给大家。我总结了几个修复经验&#xff1b; vcomp14…