持续积累ThreadLocal技术【ThreadLocal原理 + ThreadLocal的坑 + ThreadLocal的最佳实践】

持续积累ThreadLocal技术的目录

  • 一、先从使用ThreadLocal开始
    • 1、我看到的两种创建方式
      • 1.1 ThreadLocal<A> aThreadLocal = new ThreadLocal<>();
      • 1.2 ThreadLocal<A> aThreadLocal = ThreadLocal.withInitial(...)
      • 1.3 为啥需要1.2提到的创建方式?直接new不就好了?
    • 2、创建好了后,在使用aThreadLocal时,会涉及到3种重要的API
      • 2.0 在理解3种API之前,又不得不了解下ThreadLocal内部的存储结构(不清楚存储结构,何谈对其的操作?)
        • 2.0.1 3种API的概述
        • 2.0.2 ThreadLocal内部的存储结构
      • 2.1 set方法:aThreadLocal.set(...)
        • 2.1.1 JDK源码
          • 2.1.1.1 重点关注:map.set(this, value); 实现原理
            • 如何查找?(哈希表的思路)
            • 调用rehash()方法的前提条件,以及该方法的原理
          • 2.1.1.2 关注:createMap(t, value); 实现原理
      • 2.2 get方法:aThreadLocal.get()
        • 2.2.1 JDK源码
          • 2.2.1.1 重点关注:map.getEntry(this); 实现原理
      • 2.3 remove方法:aThreadLocal.remove()
        • 2.3.1 JDK源码
          • 2.3.1.1 重点关注:m.remove(this); 实现原理
  • 附录
    • 1、ThreadLocal提供的set()、get()、remove()方法,都有一个getMap()方法。在这里补充说明下。
      • 1.1 重点关注:getMap()方法的原理【ThreadLocal.java中的方法】
  • 最后

一、先从使用ThreadLocal开始

1、我看到的两种创建方式

1.1 ThreadLocal aThreadLocal = new ThreadLocal<>();

1.2 ThreadLocal aThreadLocal = ThreadLocal.withInitial(…)

1.3 为啥需要1.2提到的创建方式?直接new不就好了?

  • JDK源码中,就提到了ThreadLocal.withInitial(…):
    在这里插入图片描述

2、创建好了后,在使用aThreadLocal时,会涉及到3种重要的API

2.0 在理解3种API之前,又不得不了解下ThreadLocal内部的存储结构(不清楚存储结构,何谈对其的操作?)

2.0.1 3种API的概述
  • set方法:aThreadLocal.set(…)
  • get方法:aThreadLocal.get()
  • remove方法:aThreadLocal.remove()
2.0.2 ThreadLocal内部的存储结构
  • 图解:
    在这里插入图片描述

2.1 set方法:aThreadLocal.set(…)

2.1.1 JDK源码
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
  • 逻辑概述:一个线程Thread0去执行aThreadLocal.set(…)时,首先会取出自己的ThreadLocalMap,如果该map不存在,则创建并存储(ThreadLocal对象,value)。如果map存在,则直接存储(ThreadLocal对象,value)。
  • 对getMap(t)的解读见:“附录 1、ThreadLocal提供的set()、get()、remove()方法,都有一个getMap()方法。在这里补充说明下。”
2.1.1.1 重点关注:map.set(this, value); 实现原理
  • 方法签名:
private void set(ThreadLocal<?> key, Object value) {......
}

不贴完整源码了,毕竟在csdn上看源码并不是一个明智的决定~
对着IDEA上的源码,看本篇文章,才是不错的选择哟~

  • 思想:既然map(ThreadLocalMap)的存储结构是Entry[] table; 那显然要在table中找到一个位置table[i],将<key, value>放进去。
  • 如何查找呢?
    (1)简单的办法:遍历table。很显然JDK的设计者没有采用这种低效的方式。
    (2)哈希表的思路(学《数据结构与算法》的作用体现出来了!):对key求一个hash值,并将其转换为数组中的合法下标。
    实践:index = hash(key) % capacity (index = hash(key) & (capacity - 1) )更高效

6.1.2 哈希表简单实现 有必要学一学的~

如何查找?(哈希表的思路)
  • 回到set方法的源码:
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);

1)len是2的幂,len - 1表示成2进制,就是1…1,那么x & 1…1的范围一定是[0, 1…1],也就是数组下标的合法范围。
2)len不是2的幂,x & (len - 1)的范围也是[0, len - 1]。因为,len - 1表示为二进制,与它相与,最大情况下,便是1都没变成0,那么就是len - 1。因此,范围也是[0, len - 1]

  • 找到要放的位置了,但可能这个坑位已经被别人占了啊,这咋办?–> 遍历找下一个i。
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}

1)如果找到的位置,就是自己蹲过的坑(if (k == key) ),那么更新value即可。
2)如果找到的位置,是别人曾经蹲过的坑,但现在他跑路了,空出来的坑(if (k == null)),那么赶紧占这个坑即可:replaceStaleEntry(key, value, i); 【这种情况要多思考一下,Entry对象不为null,但key为null。这是不是ThreadLocal对象.remove()后的情况啊?】
看到remove方法就懂了~

  • 承接上文,第3种情况,找到一个从未有人蹲过的坑,那咱来蹲这个坑啊,即创建Entry对象(tab[i] = new Entry(key, value);)

  • 创建了Entry对象,意味着Entry数组中被蹲过的坑多了一个(int sz = ++size;),如果一直就这么发展下去,留给后人的坑就少了,那大家就容易内卷了,这不就冲突多了?这可不行啊。因此,有必要来消杀一波:

if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();
调用rehash()方法的前提条件,以及该方法的原理

持续更新…

2.1.1.2 关注:createMap(t, value); 实现原理
  • 源码:
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);
}
  • 如果一个线程对象Thread的threadLocals为null,那么要new一个ThreadLocalMap对象。在这个过程中,table被初始化为Entry[16]的数组(INITIAL_CAPACITY = 16,必须是2的幂)。
  • 找一个位置table[i],放入<firstKey, firstValue>
  • setThreshold(INITIAL_CAPACITY); 会进行计算:threshold = len * 2 / 3; 也就是将threshold赋值为10。

2.2 get方法:aThreadLocal.get()

2.2.1 JDK源码
public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}
  • 逻辑概述:代码结构和set()很像,都是先根据线程对象t,找到自己的ThreadLocalMap对象,如果map为null,那设置一个初始化的value值x并返回。如果map不为null,那么根据key(ThreadLocal对象)去Entry[]数组里面,找到自己的Entry对象。找不到,则返回value值x,否则返回Entry对象中的value值。
2.2.1.1 重点关注:map.getEntry(this); 实现原理
  • 方法的源码(比较短,贴一下)
private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);
}
  • 相比map.set(this, value),getEntry()方法简单一些。
    (1)同样,先在Entry数组中找到一个位置,如果这个坑位就是自己的,那直接:return e;。
    (2)如果找不到自己的坑位(e == null || e.get() != key),那么:return getEntryAfterMiss(key, i, e);

暂不研究:getEntryAfterMiss方法。目前不影响理解ThreadLocal原理。

2.3 remove方法:aThreadLocal.remove()

2.3.1 JDK源码
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}

哈哈,JDK的设计者也有搬砖的体验啊,3个方法的代码结构基本一样。
之前都这么写的:

Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {}return xxx;
  • 发现太搬砖了,就改成了:上面的写法。写简略点,早点下班回家吧~
2.3.1.1 重点关注:m.remove(this); 实现原理
  • 方法的源码(比较短,贴一下)
private void remove(ThreadLocal<?> key) {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)]) {if (e.get() == key) {e.clear();expungeStaleEntry(i);return;}}
}
  • 这里面的很多代码都在2.1.1.1 重点关注:map.set(this, value); 实现原理中见过:
private void remove(ThreadLocal<?> key) {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)]) {......}
}

不管是set还是remove,在操作之前,都要找到要操作的Entry对象。

  • 在remove方法中,找到这个Entry对象后,如果这个坑里面搬砖的人是key,那么执行:e.clear(); 以及expungeStaleEntry(i);

expungeStaleEntry(i); 先不管这个方法,目前不影响理解ThreadLocal原理。

  • 再看下e.clear();
public void clear() {this.referent = null;
}

1)在2.1.1.1 重点关注:map.set(this, value); 实现原理中,提到:如果找到的位置,是别人曾经蹲过的坑,但现在他跑路了,空出来的坑(if (k == null)),那么赶紧占这个坑即可:replaceStaleEntry(key, value, i); 【这种情况要多思考一下,Entry对象不为null,但key为null。这是不是ThreadLocal对象.remove()后的情况啊?】,在这里,我们可以有一个结论了:是的,在remove()方法中,会将key置为null。

附录

1、ThreadLocal提供的set()、get()、remove()方法,都有一个getMap()方法。在这里补充说明下。

  • 前提:我们知道ThreadLocal对象是和线程绑定的。线程为了组织<ThreadLocal对象,value>,提供了ThreadLocalMap。因此,咱先要找到线程的ThreadLocalMap对象。
  • 代码:
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);

或者直接:

ThreadLocalMap m = getMap(Thread.currentThread());

1.1 重点关注:getMap()方法的原理【ThreadLocal.java中的方法】

  • JDK源码
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}

Thread.java中,对threadLocals的定义为:ThreadLocal.ThreadLocalMap threadLocals = null;
而ThreadLocal.java和Thread.java在同一个包下:java.lang。因此,可以直接t.threadLocals。

最后

  • 2024-01-07 (1)重点了解了Thread持有的ThreadLocalMap的存储结构,本质是Entry[]数组。(2)还了解了ThreadLocal提供的3种重要且常用的API:set(…)、get()、remove()。
  • 之后还需要持续更新:(1)继续深挖ThreadLocal的其他原理,例如rehash()方法的内部细节。ThreadLocal存在的坑点,以及如何以最佳实践的方式来使用ThreadLocal。
  • 这并不是一篇传授知识的文章,而是一起学习的产物,写的不对的地方,希望大家在评论区指出,我看到后,会修正为正确的结论~ 比心

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

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

相关文章

k8s的pod基础

pod概念 pod是k8s中最小的资源管理组件。 pod也是最小化运行容器化的应用的资源管理对象。 pod是一个抽象的概念&#xff0c;可以理解为一个或者多个容器化应用的集合。 在一个pod当中运行一个容器是最常用的方式。在一个pod当中同时运行多个容器&#xff0c;在一个pod当中…

算法练习Day29 (Leetcode/Python-动态规划)

基本概念&#xff1a; 代码随想录&#xff1a; Dynamic Programming&#xff0c;简称DP&#xff0c;如果某一问题有很多重叠子问题&#xff0c;使用动态规划是最有效的。 所以动态规划中每一个状态一定是由上一个状态推导出来的&#xff0c;这一点就区分于贪心&#xff0c;贪…

计算机网络 综合(习题)

【计算机网络习题】系列文章目录 计算机网络 第一章 绪论(习题) 计算机网络 第二章 计算机网络体系结构(习题) 计算机网络 第三章 应用层(习题) 计算机网络 第四章 运输层(习题) 计算机网络 第五章 网络层(习题) 计算机网络 第六章 数据链路层(习题) 计算机网络 第七章 物…

强化学习5——动态规划在强化学习中的应用

动态规划在强化学习中的应用 基于动态规划的算法优良 &#xff1a;策略迭代和价值迭代。 策略迭代分为策略评估和策略提升&#xff0c;使用贝尔曼期望方程得到一个策略的状态价值函数&#xff1b;价值迭代直接使用贝尔曼最优方程进行动态规划&#xff0c;得到最终的最优状态价…

Unity 一文掌握使用AddListener方法为组件事件添加监听器的方法

在Unity中&#xff0c;很多组件都带有事件&#xff0c;比如: Button组件&#xff1a;onClick() Toggle组件&#xff1a;On Value Changed(Boolean) Dropdown组件&#xff1a;On Value Changed(Int32) InputField组件&#xff1a;On Value Changed(String)、On End Edit(Stri…

CCC数字钥匙设计【NFC】--NFC通信之APDU TLV

CCC3.0&#xff0c;包含NFC、BLE、UWB技术。当采用NFC通信时&#xff0c;车端与手机端是通过APDU来进行交互的。而在APDU中的data数据段&#xff0c;又可能会嵌入TLV协议的数据&#xff0c;以完成车端与手机端的通信交互。 本文先介绍APDU及TLV的一些基础知识&#xff0c;再通…

断更后的故事1

文章目录 技术男为何开始写感悟博客&#xff1f;简单的自我介绍为什么断更了默默进化的日子琐碎的事情对阶段1的思索和总结 技术男为何开始写感悟博客&#xff1f; 其实我是一个偏感性的一个技术男&#xff0c;可能这样就有点违背技术男这个定义了&#xff0c;很多时候还是挺理…

全连接网络、卷积神经网络、递归神经网络 通俗的解释

全连接网络、卷积神经网络和递归神经网络是三种不同类型的神经网络&#xff0c;它们在结构和应用上有所不同。下面我将尽量用通俗易懂的语言来解释和对比这三种神经网络。 1.全连接网络 全连接网络是一种最常见的神经网络类型&#xff0c;它的每一层都由许多神经元组成&#…

SSH 密钥身份验证和管理

安全外壳协议&#xff08;Security Shell Protocol&#xff09;是一种应用于计算机网络的安全通信协议&#xff0c;其提供的服务可用于保护网络上的连接和数据传输安全性&#xff0c;其核心思想是为网络上的两台计算机之间搭建一个安全的外壳&#xff0c;以保护数据传输的安全性…

简单介绍Java 的内存泄漏

java最明显的一个优势就是它的内存管理机制。你只需简单创建对象&#xff0c;java的垃圾回收机制负责分配和释放内存。然而情况并不像想像的那么简单&#xff0c;因为在Java应用中经常发生内存泄漏。 本教程演示了什么是内存泄漏&#xff0c;为什么会发生内存泄漏以及如何预防…

2、C语言:控制流

控制流 语句&#xff1a;在表达式后面加上分号&#xff0c;构成语句。 程序块&#xff1a;用一对花括号“{”与“}”把一组声明和语句括在一起就构成了一个复合语句。复合语句在语法上等同于单条语句。 if-else语句else-if语句&#xff1a;从上到下依次执行&#xff0c;等同于…

视频云存储/视频智能分析平台EasyCVR在麒麟系统中无法启动该如何解决?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

【docker】网络模式管理

目录 一、Docker网络实现原理 二、Docker的网络模式 1、host模式 1.1 host模式原理 1.2 host模式实操 2、Container模式 2.2 container模式实操 3、none模式 4、bridger模式 4.1 bridge模式的原理 4.2 bridge实操 5、overlay模式 6、自定义网络模式 6.1 为什么需要…

017、使用包、单元包及模块来管理日渐复杂的项目

在编写较为复杂的项目时&#xff0c;合理地对代码进行组织与管理很重要&#xff0c;因为我们不太可能记住代码中所有的细枝末节。只有按照不同的特性来组织或分割相关功能的代码&#xff0c;我们才能够清晰地找到实现指定功能的代码片段&#xff0c;或确定哪些地方需要修改。 到…

【UML】第14篇 协作图

目录 一、协作图的概述 二、协作图的主要构成 2.1 对象 2.2 消息 2.3 链 三、协作图如何画 3.1 思路 3.2 步骤 这个系列暂停了好几天了&#xff0c;适当时候再恢复一下。 UML非常经典&#xff0c;只要在这个行业&#xff0c;代码可能不会写一辈子&#xff0c;但是图肯定…

Java socket编程学习笔记

一、初步了解 1、简易代码(存在socket提前关闭问题) 服务端代码: import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.StandardCharsets;public class MySocketServer {public static void main(String[] args) throws IOEx…

js判断是否为数字的方法

找到一个比较好用的方法&#xff0c;记录下来&#xff0c;方便以后使用查找 function isNumber(value) {return !isNaN(parseFloat(value)) && isFinite(value); }目前测试情况&#xff1a; isNumber(123) —> true isNumber(12.3) —> true isNumber(-12.3) —…

阿里2面:万亿级消息,如何做存储设计?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、shein 希音、百度、网易的面试资格&#xff0c;小伙伴在面阿里时&#xff0c;遇到了一个存储设计相关的面试题&#xff1a; 存储架构&#xff0c;…

Ubuntu 虚拟机挂接 Windows 目录

Windows 共享目录 首先 Windows 下共享目录 我这里偷懒直接直接 Everyone &#xff0c;也可以指定用户啥的 Ubuntu 挂接 挂接命令&#xff0c;类似如下&#xff1a; sudo mount -o usernamefananchong,passwordxxxx,uid1000,gid1000,file_mode0644,dir_mode0755,dynperm //…

leetcode09-机器人能否返回原点

题目链接&#xff1a; https://leetcode.cn/problems/robot-return-to-origin/?envTypestudy-plan-v2&envIdprogramming-skills 思路&#xff1a; 循环遍历&#xff0c;模拟即可 代码&#xff1a; class Solution {public boolean judgeCircle(String moves) {int n m…