《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.hqwc.cn/news/282595.html

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

相关文章

Vue中插槽的使用

目录 一、默认插槽 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;代码展示 &#xff08;3&#xff09;后备内容 二、具名插槽 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;代码展示 三、作用域插槽 &#xff08;1&#xff09;概念 &#xff0…

GoWin FPGA, GPIO--- startup1

一个Bank只能用一个电压&#xff0c;假如同一个Bank&#xff0c;在引脚里设置不同的电压&#xff0c;编译不过。 解释说明 2. 错误引脚限制 以上编译设置会导致编译错误。

LeedCode刷题---滑动窗口问题(二)

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C/C》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、将X减到0的最小操作数 题目链接&#xff1a;将 x 减到 0 的最小操作数 题目描述 给你一个整数数组 nums 和一个整数 x 。每一…

教师如何维护学生的自尊心

作为教师&#xff0c;我们不仅要传授知识&#xff0c;更要关心学生的身心健康&#xff0c;特别是他们的自尊心。自尊心是个人自我价值的重要体现&#xff0c;对学生的学习、生活和未来的发展都有深远的影响。因此&#xff0c;维护学生的自尊心是教师的重要责任。 教师要尊重每…

leetcode-138-随机链表的复制(Java实现)

题目&#xff1a; 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成&#xff0c;其中每个新节点的值都设为其对应的原节点…

基于YOLOv8深度学习的路面标志线检测与识别系统【python源码+Pyqt5界面+数据集+训练代码】目标检测、深度学习实战

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

【vtkWidgetRepresentation】第十三期 VTKCaptionRepresentation

很高兴在雪易的CSDN遇见你 VTK技术爱好者 QQ:870202403 前言 本文分享三维建模中的文字标注,希望对各位小伙伴有所帮助! 感谢各位小伙伴的点赞+关注,小易会继续努力分享,一起进步! 你的点赞就是我的动力(^U^)ノ~YO 目录 前言 1. vtkCaptionRepresentation

Apache SeaTunne简介

Apache SeaTunne简介 文章目录 1.Apache SeaTunne是什么&#xff1f;1.1[官网](https://seatunnel.apache.org/)1.2 项目地址 2.架构3.特性3.1 丰富且可扩展的连接器和插件机制3.2 支持分布式快照算法以确保数据一致性3.3 支持流、批数据处理&#xff0c;支持全量、增量和实时数…

破译模式:模式识别在计算机视觉中的作用

一、介绍 在当代数字领域&#xff0c;计算机视觉中的模式识别是关键的基石&#xff0c;推动着众多技术进步和应用。本文探讨了计算机视觉中模式识别的本质、方法、应用、挑战和未来趋势。通过使机器能够识别和解释视觉数据中的模式&#xff0c;模式识别不仅推动了计算机视觉领域…

[Verilog] Verilog 数值表示

主页&#xff1a; 元存储博客 文章目录 前言1. 整数表示1.1 整数数据类型1.2 整数转换函数 2. 负数表示3. 实数表示4. 逻辑电平表示5. 逻辑值表示6. 字符表示法7. 字符串表示 前言 Verilog中&#xff0c;可以使用多种方式表示数值。 1. 整数表示 1.1 整数数据类型 基数格式…

【深度学习目标检测】四、基于深度学习的抽烟识别(python,yolov8)

YOLOv8是一种物体检测算法&#xff0c;是YOLO系列算法的最新版本。 YOLO&#xff08;You Only Look Once&#xff09;是一种实时物体检测算法&#xff0c;其优势在于快速且准确的检测结果。YOLOv8在之前的版本基础上进行了一系列改进和优化&#xff0c;提高了检测速度和准确性。…

信号量机制理论详解专题

一文学懂信号量机制的各种大题&#xff0c;详细操作见下文~ 1965年&#xff0c;荷兰学者Dijkstra提出的信号量&#xff08;Semaphores&#xff09;机制是一种卓有成效的进程同步工具。在长期且广泛的应用中&#xff0c;信号量机制又得到了很大的发展&#xff0c;它从整型信号量…