《ThreadLocal使用与学习总结:2023-12-15》史上最详细由浅入深解析ThreadLocal

由浅入深全面解析ThreadLocal

目录

  • 由浅入深全面解析ThreadLocal
    • 简介
    • 基本使用
    • ThreadLocal与synchronized的区别
    • ThreadLocal现在的设计(JDK1.8)
    • ThreadLocal核心方法源码分析
    • ThreadLocalMap源码分析
    • 弱引用与内存泄露(内存泄漏和弱引用没有直接关系)
    • ThreadLocal核心源码(Hash冲突解决)

简介

  1. 线程并发:在多线程并发的场景下使用
  2. 传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
  3. 线程隔离:每个线程的变量都是独立的,不会相互影响

基本使用

  1. 常用方法
    在这里插入图片描述

  2. 代码案例实现
    (1) 不使用ThreadLocal时模拟多线程存取数据

public class ThreadLocalDemo1 {private String content;public String getContent() {return content;}public void setContent(String content) {this.content = content;}public static void main(String[] args) {ThreadLocalDemo1 threadLocalDemo = new ThreadLocalDemo1();for (int i = 0; i < 5; i++) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {/*** 每一个线程存一个变量,过一会取出这个变量*/threadLocalDemo.setContent(Thread.currentThread().getName() + "的数据");System.out.println("------------------------");System.out.println(Thread.currentThread().getName() + "----->" + threadLocalDemo.getContent());}});thread.setName("线程" + i);thread.start();}}
}

结果:

------------------------
线程0----->线程4的数据
------------------------
线程4----->线程4的数据
------------------------
线程2----->线程4的数据
------------------------
线程3----->线程4的数据
------------------------
线程1----->线程4的数据

(2) 使用ThreadLocal对多线程进行数据隔离,把数据绑定到ThreadLocal
(传统解决方案首先想到的就是加锁,确实可以实现,但是却牺牲了效率,需要等待上一个线程之行结束才可以往下之行)

public class ThreadLocalDemo2 {ThreadLocal<String> threadLocal = new ThreadLocal<>();private String content;public String getContent() {return threadLocal.get();}public void setContent(String content) {threadLocal.set(content);}public static void main(String[] args) {ThreadLocalDemo2 threadLocalDemo2 = new ThreadLocalDemo2();for (int i = 0; i < 5; i++) {Thread thread = new Thread(new Runnable() {@Overridepublic void run() {/*** 每一个线程存一个变量,过一会取出这个变量*/threadLocalDemo2.setContent(Thread.currentThread().getName()+"的数据");System.out.println("------------------------");System.out.println(Thread.currentThread().getName() + "----->" + threadLocalDemo2.getContent());}});thread.setName("线程" + i);thread.start();}}
}

结果:

------------------------
------------------------
------------------------
线程3----->线程3的数据
------------------------
线程2----->线程2的数据
线程1----->线程1的数据
线程0----->线程0的数据
------------------------
线程4----->线程4的数据

ThreadLocal与synchronized的区别

二者都是用来处理多线程并发访问的问题,但是二者的原理和侧重点不一样,简要说就是,ThreadLocal牺牲了空间,而synchronized是牺牲了时间来保证线程安全(隔离)。
在这里插入图片描述
总结:在上述的案例当中,使用ThreadLocal更为合理,这样保证了程序拥有了更高的并发性。

ThreadLocal现在的设计(JDK1.8)

  1. 简介
    每一个Thread维护一个ThreadLocalMap,这个Map的key为ThreadLocal实例本身,而value则为实际存储的值。
  2. 具体过程
    (1)每一个Thread内部都有一个Map(ThreadLocalMap)
    (2)Map里面存储的ThreadLocal对象(key)和线程的变量副本(value)
    (3)Thread的Map是由ThreadLocal来维护的,由ThreadLocal负责向Map获取和设置线程的变量值。
    (4)对于线程获取值,每一个副本只能获取当前线程本地的副本值,别的线程无法访问到,互不干扰,实现了线程隔离。
  3. 对比与1.8之前的设计(相当于Thread与ThreadLocal的角色互换了)
    在这里插入图片描述
  4. 1.8设计的好处
    (1)每个Map存储的Entry数量变少了(因为实际状况下Thread比ThreadLocal多)
    (2)当Thread销毁时,ThreadLocalMap也会随之销毁,避免内存的浪费

ThreadLocal核心方法源码分析

在这里插入图片描述

  1. get方法源码
public T get() {// 获取当前线程Thread t = Thread.currentThread();// 获取ThreadLocalMapThreadLocalMap map = getMap(t);// map不为空时,获取里面的Entryif (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;// 返回结果return result;}}// 没有则赋值初始值null并返回return setInitialValue();}
  1. set方法源码
public void set(T value) {// 获取当前线程Thread t = Thread.currentThread();// 获取ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {// 不为空直接setmap.set(this, value);} else {// map为空则创建并setcreateMap(t, value);}}
  1. initialValue方法返回初始值(protected修饰为了让子类覆盖设计的)需要自定义初始值可以重写该方法
protected T initialValue() {return null;}
  1. remove方法
public void remove() {// 获取当前线程的ThreadLocalMapThreadLocalMap m = getMap(Thread.currentThread());if (m != null) {// 移除ThreadLocalMapm.remove(this);}}
  1. setInitialValue方法
private T setInitialValue() {// 得到初始化值nullT value = initialValue();// 获取当前线程Thread t = Thread.currentThread();// 获取线程中ThreadLocalMapThreadLocalMap map = getMap(t);// map存在的话把null设置进去,不存在则创建一个并将null设置进去if (map != null) {map.set(this, value);} else {createMap(t, value);}// 如果当前ThreadLocal属于TerminatingThreadLocal(关闭的ThreadLocal)则register(注册)到TerminatingThreadLocalif (this instanceof TerminatingThreadLocal) {TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);}return value;}

ThreadLocalMap源码分析

  1. 简介
    ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,是独自设计实现Map功能,内部的Entry也是独立的。
  2. 结构图解
    在这里插入图片描述
  3. 成员变量
		// Entry类,继承弱应用,为了和Thread的生命周期解绑static class Entry extends WeakReference<ThreadLocal<?>> {/** 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.* 根据需要调整大小。长度必须是2的幂。*/private Entry[] table;/*** The number of entries in the table.* table中的entrie数量*/private int size = 0;/*** The next size value at which to resize.* 要调整大小的下一个大小值*/private int threshold; // Default to 0/*** Set the resize threshold to maintain at worst a 2/3 load factor.* 设置调整大小阈值以维持最坏的2/3负载因子*/private void setThreshold(int len) {threshold = len * 2 / 3;}/*** Increment i modulo len.* 增量一*/private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}/*** Decrement i modulo len.* 减量一*/private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}

弱引用与内存泄露(内存泄漏和弱引用没有直接关系)

  1. 内存泄漏/溢出概念
    (1)Memory overflow:内存溢出,没有足够的空间提供给申请者使用
    (2)Memory leak:内存泄漏,系统中已动态分配的堆内存由于某种原因无法释放或者没有释放,导致系统内存堆积,影响系统运行,甚至导致系统崩溃。内存泄漏终将导致内存溢出。
  2. 强/弱引用概念
    (1)Strong Referce:强引用,我们常见的对象引用,只要有一个强引用指向对象,也就表明还“活着”,这种状况下垃圾回收机制(GC)是不会回收的。
    (2)Weak Referce:弱引用,继承了WeakReferce的对象,垃圾回收器发现了只具有弱引用的对象,不管当前系统的内存是否充足,都会回收他的内存。
  3. 如果key,即Entry使用强引用,也无法避免内存泄漏
    因为Entry是在Thread当前线程中,生命周期和Thread一样,没有手动删除Entry时Entry就会内存泄漏。
  4. 也就是说,只要在调用完ThreadLocal后及时使用remove方法,才能避免内存泄漏
    在这里插入图片描述
    在这里插入图片描述

ThreadLocal核心源码(Hash冲突解决)

  1. 从构造方法入手
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {// 初始化tabletable = new Entry[INITIAL_CAPACITY];// 计算索引在数组中的位置(核心代码)int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);// 设置值table[i] = new Entry(firstKey, firstValue);size = 1;// 设置阈值(INITIAL_CAPACITY的三分之二)setThreshold(INITIAL_CAPACITY);}
  1. 重点分析int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
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;public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta);}

(1)这里定义了一个AtomicInteger,每次获取并加上HASH_INCREMENT(0x61c88647,这个值与斐波那契数(黄金分割)有关),是为了让哈希码能够均匀的分布在2的n次方的数组(Entry[])里面,也就尽可能避免了哈希冲突。
(2)hashcode & (INITIAL_CAPACITY - 1) 相当于hashcode % (INITIAL_CAPACITY - 1) 的高效写法,所以size必须为2的次幂,这样最大程度避免了哈希冲突。

  1. set方法源码分析
private void set(ThreadLocal<?> key, Object value) {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)]) {// 获取到该Entry对应的ThreadLocalThreadLocal<?> k = e.get();if (k == key) {// key存在则覆盖valuee.value = value;return;}// key为null但是值不为null,这说明了之前使用过,但是ThreadLocal被垃圾回收了,当前的Entry是一个陈旧的(Stale)元素if (k == null) {// key(ThreadLocal)不存在,则新Entry替换旧的Entry,此方法做了不少垃圾清理的动作,避免了内存泄漏。replaceStaleEntry(key, value, i);return;}}// ThreadLocal中未找到key也没有陈旧的元素,此时则在这个位置新创建一个Entrytab[i] = new Entry(key, value);int sz = ++size;// cleanSomeSlots用于清理e.get()为null的key,如果大于阈值(2/3容量)则rehash(执行一次全表扫描清理工作)if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}/*** 线性探测法查找元素,到最后一个时重定位到第一个*/private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}

(详细视频可前往B站黑马程序员)

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

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

相关文章

3.3 右值引用:移动语义与完美转发

一、移动语义的引入 1.浅拷贝带来的问题 对于存在资源(文件&#xff0c;指针)类型成员的类对象&#xff0c;在执行拷贝的时候存在资源管理的问题&#xff0c;这是浅拷贝导致的。 此时我们可以重写拷贝构造实现深拷贝&#xff0c;解决资源管理的问题。 但是有的时候拷贝并不…

【QT】QDockWidget控件的使用

目录 1.概述 2.常用函数介绍 3.QDockWidget布局相关 4.QDockWidget的使用注意事项 5.使用场景 6.简单应用示例代码 1.概述 QDockWidget类提供了一个小部件&#xff0c;可以停靠在QMainWindow中&#xff0c;也可以作为桌面上的顶级窗口浮动。 QDockWidget提供了dock Widg…

EMC测试与整改实践?|深圳比创达电子

电磁兼容(EMC)测试和整改是当今社会对电磁兼容(EMC)意识日益深入的表现&#xff0c;EMC测试与整改随着社会对电磁环境要求的不断提高&#xff0c;越来越受到重视&#xff0c;下面就EMC测试与整改实践进行一下详细介绍。 一、什么是EMC测试&#xff1f; EMC测试是指在一定的电…

减速机振动相关标准 - 笔记

参考标准&#xff1a;国家标准|GB/T 39523-2020 减速机的振动标准与发动机不同&#xff0c;摘引&#xff1a; 原始加速度传感器波形 可以明显看到调幅波 它的驱动电机是300Hz~2000Hz范围的。这个采样时间是5秒&#xff0c;看分辨率至少1024线。可分出500条谱线。 频谱部分 …

干货分享 | 如何在TSMaster中对常用总线报文信号进行过滤?

TSMaster软件平台支持对不同总线&#xff08;CAN、LIN、FlexRay&#xff09;的报文和信号过滤&#xff0c;过滤方法一般有全局接收过滤、数据流过滤、窗口过滤、字符串过滤、可编程过滤&#xff0c;针对不同的总线信号过滤器的使用方法也基本相同。今天重点和大家分享一下关于T…

魔众文库系统v5.8.0版本发布:水印、分类与移动端升级,打造更高效文档管理体验

魔众文库系统迎来了全新的v5.8.0版本更新&#xff01;此次更新不仅对水印功能进行了升级&#xff0c;还新增了辅助分类样式&#xff0c;同时优化了移动端体验。让我们一起来看看这次更新的亮点吧&#xff01; 一、水印功能全新升级 在v5.8.0版本中&#xff0c;魔众文库系统的…

Spring学习?这一篇文章就够,史上最全!

文章目录 前言一、IOC概述及底层原理1.概述和原理 二、思路分析三、IOC操作bean管理&#xff08;基于xml&#xff0c;使用的是IDEA2022.3.3&#xff0c;maven仓库&#xff09;1.xml实现bean管理&#xff08;1&#xff09;创建对象&#xff08;2&#xff09;注入属性&#xff08…

[HTML]Web前端开发技术3(HTML5、CSS3、JavaScript )超链接,target,scrolling,marginwidth,frameborder,iframe——喵喵画网页

希望你开心&#xff0c;希望你健康&#xff0c;希望你幸福&#xff0c;希望你点赞&#xff01; 最后的最后&#xff0c;关注喵&#xff0c;关注喵&#xff0c;关注喵&#xff0c;佬佬会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的…

c语言:[输出函数]与[输入函数]|要点简述

一、【输出函数】 printf() 与 puts()的不同点 1、printf()函数 printf()支持单个字符%c的输出&#xff0c;以及字符串%s的输出。 (1)如果是以%c的形式输出&#xff0c;是一个字符一个字符的输出。因此&#xff0c;要用一个循环语句&#xff0c;把字符逐个输出。 (2)而用%…

骨传导耳机和气传导耳机有什么区别?谁更值得入手?

先说答案&#xff0c;骨传导耳机和气传导耳机的佩戴方式和传声方式不同&#xff0c;并且骨传导耳机相比于气传导耳机更值得入手。 一、骨传导耳机和气传导耳机有什么区别 1、佩戴方式不同 骨传导耳机采用一体式耳挂佩戴或耳夹式佩戴&#xff0c;气传导耳机采用分体式耳挂设计…

羊大师揭秘,皮肤保湿不如喝点羊奶?

羊大师揭秘&#xff0c;皮肤保湿不如喝点羊奶&#xff1f; 在寒冷的冬季&#xff0c;人们常常会发现自己的皮肤变得干燥、粗糙&#xff0c;甚至出现裂纹。而这时候&#xff0c;大家或许很难联想到喝点羊奶能够改善这一问题。但是小编羊大师发现&#xff0c;事实上羊奶确实可以…

前端开发中的webpack打包工具

前端技术发展迅猛&#xff0c;各种可以提高开发效率的新思想和框架层出不穷&#xff0c;但是它们都有一个共同点&#xff0c;即源代码无法直接运行&#xff0c;必须通过转换后才可以正常运行。webpack是目前主流的打包模块化JavaScript的工具之一。 本章主要涉及的知识点有&am…

高中python语言常用语句,高中python教程标准

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;高中python语言常用语句&#xff0c;高中python教程标准&#xff0c;现在让我们一起来看看吧&#xff01; 大家好&#xff0c;本文将围绕高中python语言常用语句展开说明&#xff0c;高中python例题和答案是一个很多人都…

Axure之交互与情节与一些实例

目录 一.交互与情节简介 二.ERP登录页到主页的跳转 三.ERP的菜单跳转到各个页面的跳转 四.省市联动 五.手机下拉加载 今天就到这里了&#xff0c;希望帮到你哦&#xff01;&#xff01;&#xff01; 一.交互与情节简介 "交互"通常指的是人与人、人与计算机或物体…

第二证券:股票交易时间以及规则是什么?

股票生意时间以及规则是什么&#xff1f; 1、股票生意时间 周一至周五上午9&#xff1a;30-11:30&#xff0c;下午13:00-15:00&#xff0c;周末以及法定节假日休市不进行生意。可是不生意不代表不能进行托付&#xff0c;股票在清算之后投资者就能够进行托付。股票的清算时间&…

一招教你将logo背景变透明,省时又方便!

透明背景的Logo可以与不同的背景颜色或图像融合&#xff0c;而不会出现突兀或不协调的感觉&#xff0c;这使得Logo在各种媒体和设计中更加灵活和多用途&#xff0c;想要把图片去背景变透明的方法有很多&#xff0c;比如最常见的就是利用ps软件来处理&#xff0c;不过这个图片去…

308 Permanent Redirect的一种可能解决方案:检查一下请求路径

两条斜线导致请求可能自动定向到https了&#xff1f;反正给改成一条斜线就好了

我的4096创作纪念日

机缘 岁月如梭&#xff0c;时光一晃已经在CSDN扎根4096天了。第一次注册CSDN好像还是在2012年&#xff0c;那会还没大学毕业。初入CSDN&#xff0c;只是把他当作自己编程时遇到问题的在线笔记记录而已&#xff0c;没想到无意间还帮助了其他遇到同样问题困扰的同学。而在这4096…

uni-data-checkbox无法选中

问题描述 今天在使用uni-data-checkbox时候发现文字选中了&#xff0c;单选的小圆圈没有出来。在浏览器模拟手机显示的效果和在安卓手机上显示的效果都和下面的图一样。 错误代码 <uni-data-checkbox mode"list" v-model"chooseLanguage" :localdata…

【CCF CSP】202312-2 因子化简(C/C++解题思路+满分题解)

解题思路 80分思路代码 由于题目在数据规模中说明阈值k > 1, 因此提取因式时只需要关注次数在二次以上的因式。也就是说&#xff0c;我们只需要判断从1到待化简因式的平方根是否是满足题意的因式即可。举个例子&#xff0c;假设题目所给因式是10000&#xff0c;那么只需要判…