深入解析Java ThreadLocal及其内存管理机制

在多线程编程中,如何保证线程间的数据隔离和线程安全是一个重要问题。Java提供的ThreadLocal类通过让每个线程拥有独立的数据副本,巧妙地解决了这个问题。本文将深入分析ThreadLocal的工作机制,并探讨如何防止内存泄漏。

目录

  1. ThreadLocal基本使用
  2. set方法分析
  3. get方法分析
  4. remove方法分析
  5. 设计ThreadLocal需要考虑的问题
    • 线程间数据隔离
    • 防止内存泄漏
  6. 总结

1. ThreadLocal基本使用

ThreadLocal通过为每个线程提供独立的变量副本,确保多线程环境下变量的线程安全性。以下是一个简单的使用示例:

package threadlocaltest;public class Main {private static final ThreadLocal<String> userLocal = new ThreadLocal<>();private static final ThreadLocal<String> domainLocal = new ThreadLocal<>();public String getCurrentUser() {return userLocal.get();}public void setCurrentUser(String str) {userLocal.set(str);}public String getDomain() {return domainLocal.get();}public void setDomain(String str) {domainLocal.set(str);}public static void main(String[] args) {// 获取数据System.out.println(userLocal.get());System.out.println(domainLocal.get());}
}

2. set方法分析

ThreadLocalset方法将值存储在当前线程的ThreadLocalMap中:

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}
}

关键步骤

  1. 获取当前线程。
  2. 获取线程的ThreadLocalMap对象。
  3. 如果ThreadLocalMap不为空,则调用map.set方法存储值。
  4. 如果ThreadLocalMap为空,则初始化一个新的ThreadLocalMap

ThreadLocalMap的构造方法:

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);
}

ThreadLocalMapset方法:

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)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold) {rehash();}
}

3. get方法分析

ThreadLocalget方法从当前线程的ThreadLocalMap中获取值:

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();
}

关键步骤

  1. 获取当前线程。
  2. 获取线程的ThreadLocalMap对象。
  3. 调用map.getEntry方法获取Entry。
  4. 如果Entry不为空,则返回Entry的值。
  5. 如果ThreadLocalMap为空,则初始化一个默认值。

ThreadLocalMapgetEntry方法:

private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key) {return e;} else {return getEntryAfterMiss(key, i, e);}
}

ThreadLocalMapgetEntryAfterMiss方法:

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;while (e != null) {ThreadLocal<?> k = e.get();if (k == key) {return e;}if (k == null) {expungeStaleEntry(i);} else {i = nextIndex(i, len);}e = tab[i];}return null;
}

4. remove方法分析

ThreadLocalremove方法用于从当前线程的ThreadLocalMap中移除值:

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

ThreadLocalMapremove方法:

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;}}
}

5. 设计ThreadLocal需要考虑的问题

5.1 线程间数据隔离

为了实现线程间数据隔离,Java将ThreadLocal集成到了每个线程中:

ThreadLocal.ThreadLocalMap threadLocals = null;

5.2 防止内存泄漏

内存泄漏的发生时机

当内存空间被占满且无法释放时,会导致内存泄漏。

防止内存泄漏的措施

为了防止内存泄漏,可以采取以下措施将无用的数据及时回收掉。

标识无用数据

ThreadLocal中,使用弱引用来标识无用数据。当弱引用的key被GC回收后,key会变成null,从而标识这条数据无用了。

static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}
何时回收无用数据

可以在每次put或get时检查并回收无用数据,同时提供remove方法,用户可以在适当的时候手动调用以加速回收。

private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;tab[staleSlot].value = null;tab[staleSlot] = null;size--;Entry e;int i;for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();if (k == null) {e.value = null;tab[i] = null;size--;} else {int h = k.threadLocalHashCode & (len - 1);if (h != i) {tab[i] = null;while (tab[h] != null) {h = nextIndex(h, len);}tab[h] = e;}}}return i;
}

该回收方法在getsetremove、数组扩容时都会被使用到。ThreadLocalMap的key使用弱引用本身就是优化内存泄漏的一种手段,在ThreadLocal不是全局静态使用方式时,弱引用会生效将key回收,表示这是一条被废弃的数据,但是value还被entry持有,entry被entry数组持有,所以在getset时都会触发expungeStaleEntry的回收机制,但是为了减少无用数据在内存里的停留时间,可以调用remove方法加速回收。

总结

ThreadLocal中,通过使用弱引用和expungeStaleEntry方法,可以有效防止内存泄漏。此外,提供remove方法允许用户手动清理无用数据,从而减少内存占用。这种设计思想在保证线程安全的同时,也对内存管理进行了优化。通过本文的详细分析,希望读者对ThreadLocal的工作机制和内存管理有更深入的理解。

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

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

相关文章

综合管廊挂轨巡检机器人:安全高效管理的新力量

综合管廊、电力管廊等作为承载着各类电缆和管线的重要通道&#xff0c;管廊的安全和可靠性对城市的运行至关重要。传统人工巡检效率低、劳动强度大&#xff0c;且可能存在巡检不及时、不准确等问题。难以满足日益复杂和庞大的管廊系统的监控需求。为了解决这些问题&#xff0c;…

如何选择一个好的汽车油封制造商?

汽车的每一个零部件都至关重要&#xff0c;其中&#xff0c;油封的作用更是不可忽视。它们确保了液体和气体在汽车内部的正确流动&#xff0c;防止了泄漏。因此&#xff0c;选择一个可靠的汽车油封制造商就显得尤为重要。那么&#xff0c;我们应该如何做出明智的选择呢? 首先…

MySQL用于分析查询性能的命令

1、EXPLAIN 1、使用EXPLAIN命令可以查看MySQL如何执行SQL查询语句&#xff0c;包括它是否使用了索引、使用了哪些索引、表扫描方式等。 2、示例&#xff1a;EXPLAIN SELECT * FROM table_name WHERE column_name value; 2、SHOW PROFILE 1、SHOW PROFILE命令用于显示查询执…

RabbitMQ基本概念

RabbitMQ是AMQP协议的一个开源实现&#xff0c;所以其基本概念也就是的 AMQP 协 议中的基本概念。如图3-1所示是 RabbitMQ 的整体架构图。 Message(消息):消息是不具名的&#xff0c;它由消息头和消息体组成。消息体是不透明的&#xff0c; 而消息头则由一系列可选属性组成&…

vue3中的图片懒加载指令及全局注册

vue3中的图片懒加载指令及全局注册 最近重新刷了一遍黑马的小兔鲜前端项目&#xff0c;发现有个懒加载的指令之前还没有用过。而且写法相对固定&#xff0c;因此记录一下 首先&#xff0c;懒加载&#xff08;Lazy Loading&#xff09;的作用是延迟加载某些资源或组件&#xf…

用免费的“山水博客”来管理你的离线文章

山水博客地址&#xff1a; https://github.com/opensanyue/ssblog 电脑上存了不博客文章&#xff0c;一直想找个软件整理一下。前不久在github刷到一个&#xff0c;试了一下&#xff0c;还不错&#xff0c;先看看成果。 左边我建了2个大类&#xff0c;分开用来放“csdn”和“简…

【附精彩文章合辑】哈佛辍学小哥的创业经历【挑战英伟达!00 后哈佛辍学小哥研发史上最快 AI 芯片,比 H100 快 20 倍!】

前情提要 https://blog.csdn.net/weixin_42661676/article/details/140020491 哈佛辍学小哥的创业经历 一、背景与起步 这位哈佛辍学小哥&#xff0c;名为Chris Zhu&#xff0c;是一位华裔学生&#xff0c;他在2020年进入哈佛大学&#xff0c;攻读数学学士学位和计算机科学硕…

C#面: C# 如何从基类创建派生类对象?

在C#中&#xff0c;可以通过继承来创建派生类对象。继承是面向对象编程中的一种重要概念&#xff0c;它允许一个类&#xff08;称为派生类&#xff09;继承另一个类&#xff08;称为基类&#xff09;的属性和方法。 要从基类创建派生类对象&#xff0c;首先需要定义一个派生类…

文物管理技术RFID技术

随着科技的不断发展&#xff0c;科技在各个领域都发挥着重要的作用。其中&#xff0c;在文物管理方面&#xff0c;RFID技术的应用正在逐渐引起人们的关注。RFID&#xff08;Radio Frequency Identification&#xff09;技术是一种通过无线电信号进行非接触式识别的技术&#xf…

docker curl:(56) Recv failure: Connection reset by peer

docker容器启动后&#xff0c;查看日志未发现错误&#xff0c;通过查询和分析&#xff0c;发现是期望容器打开的端口与容器实际打开的端口不一致导致。 1&#xff09;docker run -itd -p 8082:8082 vulfocus/log4j2-rce-2021-12-09:latest 2&#xff09;curl localhost:8082 …

java基于ssm+jsp 大学生校园兼职系统

1前台首页功能模块 大学生校园兼职系统&#xff0c;在大学生校园兼职系统可以查看首页、企业信息、招聘信息、论坛信息、留言反馈、我的、跳转到后台等内容&#xff0c;如图1所示。 图1系统首页界面图 学生登录&#xff0c;通过学生登录填写账号、密码等信息进行登录操作&…

OSM数据导入至PostgreSQL

好几年没写博客了&#xff0c;最近博士小论文扩展准备添加个路网数据增加定位准确性 用的读取代码是github上的代码&#xff0c;使用openstreet数据。 1&#xff0c;从BBBbike划定区域下载路网数据&#xff0c;BBBike extracts OpenStreetMap (OSM, Garmin, Shapefile etc.) …

【Proteus仿真】基于stm32的数码管时钟

【Proteus仿真】基于stm32的数码管时钟 Proteus仿真&#xff01;基于stm32的数码管时钟~_哔哩哔哩_bilibili ‍ 01原理图 ​​ 02功能描述 1.通过按键修改时间 2.数码管显示实时时间&#xff0c;时-分-秒-毫秒格式 03获取方式 https://docs.qq.com/sheet/DTExIc2dPUUJ…

启动台出现agent app的解决方法~

启动台出现agent app的解决方法&#xff5e; 如果用了战网&#xff0c;Battle.net&#xff0c;在卸载后有一个agent app&#xff0c;启动台删除不掉&#xff0c;应用程序里面没有&#xff0c;怎么办呢&#xff1f; 解决方法&#xff1a;找到这个app所在位置&#xff0c;可以通…

05 Shell编程之免交互

1、Here Document免交互 1.1 Here Document概述 Here Document是一个特殊用途的代码块&#xff0c;它是标准输入的一种替代品&#xff0c; 可以帮助脚本开发人员不必使用临时文件来构建输入信息&#xff0c;而是直接就地生产出一个文件并用作命令的标准输入。 Here Documen…

力扣503

题目 给定一个循环数组 nums &#xff08; nums[nums.length - 1] 的下一个元素是 nums[0] &#xff09;&#xff0c;返回 nums 中每个元素的 下一个更大元素 。 数字 x 的 下一个更大的元素 是按数组遍历顺序&#xff0c;这个数字之后的第一个比它更大的数&#xff0c;这意味…

vue-主题切换

themeName/index.vue页面: <template><div class"theme-view"><div click"themeClick" class"theme-btn">切换颜色</div><br>{{themeName white ? 白色 : 深色}}主题页面</div> </template><sc…

Spring Boot中最佳实践:数据源配置详解

Spring Boot中最佳实践&#xff1a;数据源配置详解 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01;今天我们将深入探讨在Spring Boot中如何进行最佳实践的数据源…

po文件并转换成mo文件

po文件转换成mo文件 简介 .po和.mo文件是WordPress中语言相关的两种文件。po 是Portable Object(可移植对象)的缩写&#xff0c;存放待翻译的字符串信息&#xff0c;可直接用文本编辑器打开编辑&#xff1b;mo 是Machine Object的缩写&#xff0c;二进制文件&#xff0c;程序…

PHP框架中的模型:核心组件解析

引言 PHP框架作为现代Web开发的强大工具&#xff0c;极大地提高了开发效率和应用质量。在众多PHP框架中&#xff0c;模型&#xff08;Model&#xff09;扮演着至关重要的角色。本文将深入探讨模型在PHP框架中的作用、重要性以及它如何与其他组件协同工作。 什么是模型&#x…