用ThreadLocal解决线程隔离问题

news2024/9/20 5:39:22

存在的以下代码所示的线程隔离问题:

package study.ThreadLocal解决线程隔离问题;

/*
    线程隔离 - 在多线程并发场景下,每个线程的变量都应该是相互独立的
    线程A:设置(变量1) 获取(变量1)
    线程B:设置(变量2) 获取(变量2)
    
 */

public class 存在的线程隔离问题 {
    private String content;

    private String getContent() {
        return content;
    }

    private void setContent(String content) {
        this.content = content;
    }

    public static void main(String[] args) {
        存在的线程隔离问题 demo = new 存在的线程隔离问题();
		// 多个线程同时访问和修改同一个 存在的线程隔离问题 类对象 demo 的属性 content,这会导致线程之间的竞争问题。
		// 由于 content 变量是共享的,并且没有任何同步控制,所以多个线程可能会相互覆盖彼此的数据,从而导致不可预测的结果。
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    /*
                        每个线程:存一个变量,过一会再取出这个变量
                     */
                    demo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println("---------------------------");
                    System.out.println(Thread.currentThread().getName() + "------>" + demo.getContent());
                }
            });

            thread.setName("线程" + i); // 给每个线程设置线程名
            thread.start();
        }
    }
}

结果:
在这里插入图片描述

使用ThreadLocal解决上面线程隔离问题。

package study.ThreadLocal解决线程隔离问题;

/*
    线程隔离 - 在多线程并发场景下,每个线程的变量都应该是相互独立的
    线程A:设置(变量1) 获取(变量1)
    线程B:设置(变量2) 获取(变量2)

    ThreadLocal:
    1. set() : 将变量绑定到当前线程中
    2. get() : 获取当前线程绑定的变量
 */

public class 存在的线程隔离问题 {

    ThreadLocal<String> tl = new ThreadLocal<>();

    private String content;

    private String getContent() {
        return tl.get();
    }

    private void setContent(String content) {
        tl.set(content);
//        this.content = content;
    }

    public static void main(String[] args) {
        存在的线程隔离问题 demo = new 存在的线程隔离问题();
        // 多个线程同时访问和修改同一个 存在的线程隔离问题 类对象 demo 的属性 content,这会导致线程之间的竞争问题。
        // 由于 content 变量是共享的,并且没有任何同步控制,所以多个线程可能会相互覆盖彼此的数据,从而导致不可预测的结果。
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    /*
                        每个线程:存一个变量,过一会再取出这个变量
                     */
                    demo.setContent(Thread.currentThread().getName() + "的数据");
                    System.out.println("---------------------------");
                    System.out.println(Thread.currentThread().getName() + "------>" + demo.getContent());
                }
            });

            thread.setName("线程" + i); // 给每个线程设置线程名
            thread.start();
        }
    }
}

结果:
在这里插入图片描述
如果使用synchronized也可以完成一样的效果,但是却牺牲了程序的并发性。

synchronized(存在的线程隔离问题.class) {
	demo.setContent(Thread.currentThread().getName() + "的数据");
    System.out.println("---------------------------");
    System.out.println(Thread.currentThread().getName() + "------>" + demo.getContent());
}

在这里插入图片描述


另一个需求场景:
事务的使用注意点:

  1. service层和dao层的连接对象保持一致
  2. 每个线程的connection对象必须前后一致,并且不同线程之间线程隔离,你处理你的,我处理我的,互不干扰。

常规的解决方案:

  1. 传参:将service层的connection对象直接传递到dao层
  2. 加锁

弊端

  1. 将 Connection 对象直接从 service 层传递到 DAO 层确实会提高耦合度,这是因为这种做法违反了分层架构的原则,使各层之间的依赖关系更加紧密。耦合度(Coupling)是指两个模块或类之间互相依赖的程度。高耦合意味着模块之间的依赖关系较强,任何一个模块的变化都会影响到其他模块。低耦合则意味着模块之间的依赖关系较弱,模块可以独立地进行修改和维护。
  2. 因为存在加锁的同步,使得在多线程环境下降低并发度从而降低程序性能。

使用ThreadLocal相比常规的解决方案的优势

  1. 传递数据:保存每个线程绑定的数据,在需要的地方可以直接获取,避免参数直接传递带来的代码耦合问题
  2. 线程隔离:各线程之间的数据相互隔离又兼具并发性,避免同步方式带来的性能损失

内部结构

在这里插入图片描述

  • 早期的ThreadLocal设计是ThreadLocal来维护ThreadLocalMap,每个Thread类线程对象作为Map的Key
  • 而JDK8的设计是让每一个Thread类线程对象来维护ThreadLocalMap,当前线程的每一个ThreadLocal作为Map的Key

好处:

  1. 实际开发中,ThreadLocal的数量往往少于线程Thread的数量,所以对于JDK8这种设计来说,每个Map存储的Entry的数量就会变少,这样就可以尽量避免哈希冲突的发生
  2. 当Thread销毁的时候,ThreadLocal也会随之销毁,从而能够及时回收内存

核心方法

在这里插入图片描述

set方法

  1. 首先获取当前线程,并根据当前线程获取它的Map
  2. 如果获取的Map不为空,则将此参数设置到Map中(当前ThreadLocal的引用作为Key)
  3. 如果Map为空,则给该线程创建Map,并设置初始值
/ *
	设置当前线程对应的ThreadLocal的值
	value是将要保存在当前线程对应的ThreadLocal的值
*/
public void set(T value) {
		// 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取当前线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 判断map是否存在
        if (map != null) {
        	// 存在则调用map.set设置此实体entry
        	// 当前的ThreadLocal作为Key
        	// 当前线程需要绑定的值作为Value
            map.set(this, value);
        } else {
        	// 1) 当前线程Thread不存在ThreadLocalMap对象
        	// 2) 则调用createMap进行ThreadLocalMap对象初始化
        	// 3) 并将t(当前线程)和value(t对应的值)作为第一个entry存放进ThreadLocalMap中
            createMap(t, value);
        }
    }

/* 
	获取当前线程Thread对应维护的ThreadLocalMap
	t就是当前线程
	返回对应维护的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
// Thread.java源码中,关于threadLocals的描述如下
/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
   
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

get方法

  1. 首先获取当前线程,根据当前线程获取一个Map
  2. 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来从Map中获取对应的Enrty e,否则转到4.
  3. 如果e不为null,则返回e.value,否则转到4.
  4. Map为空或者e为空,则通过initialValue函数获取初始值value(子类不重写的话,默认就是null了),然后用ThreadLocal的引用和初始值value作为新建Map的第一个key和value。
/*
	返回当前线程中保存的ThreadLocal的值
	如果当前线程没有此ThreadLocal变量,则它会通过initialValue方法进行初始化
	返回当前线程对应的ThreadLocal的值
*/
public T get() {
		// 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果此map存在
        if (map != null) {
        	// 因为map的Key是用ThreadLocal作为Key
        	// 所以以当前的ThreadLocal为Key,调用getEntry获取对应的存储实体e
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 对e进行判空
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 获取存储实体e对应的value值
                // 即为我们想要的当前线程对应ThreadLocal绑定的那个值
                T result = (T)e.value;
                return result;
            }
        }
        /*
			会进行如下初始化的两种情况
			第一种情况:map不存在,表示此线程没有维护的ThreadLocalMap对象
			第二种情况:map存在,但是没有与当前ThreadLocal关联的entry
		*/
        return setInitialValue();
    }

/*
	初始化
	返回初始化后的值
*/
 private T setInitialValue() {
 		// 调用initialValue获取初始化后的值
 		// 此方法可以被子类重写,如果不重写默认返回null
        T value = initialValue();
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取当前线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 判断map是否存在
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

remove方法

  1. 首先获取当前线程,并根据当前线程获取一个属于那个线程的Map
  2. 如果获取的Map不为空,则移除当前ThreadLocal对象对应的键值对entry,如果Map中不存在当前ThreadLocal对象对应的键值对entry,则不用管
/*
	删除当前线程中保存的ThreadLocal对应的实体entry(也就是键值对)
*/
public void remove() {
		 // 获取当前线程对象中维护的ThreadLocalMap对象
         ThreadLocalMap m = getMap(Thread.currentThread());
         // 如果此map存在
         if (m != null) {
         	// 存在则调用map.remove
         	// 以当前ThreadLocal为key删除对应的实体entry
             m.remove(this);
         }
     }

在这里插入图片描述


存在的内存泄漏问题

Memory overflow:内存溢出,没有足够的内存提供申请者使用
Memory leak:内存泄漏是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。内存泄漏的堆积终将导致内存溢出。
在这里插入图片描述

在这里插入图片描述

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

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

相关文章

【数据结构与算法】希尔排序

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​

Go语言--复合类型之map、结构体

map Go 语言中的 map(映射、字典)是一种内置的数据结构&#xff0c;它是一个无序的 key-value 对的集合&#xff0c;比如以身份证号作为唯一键来标识一个人的信息。 格式 map [keyType]valueType 在一个 map 里所有的键都是唯一的&#xff0c;而且必须是支持和!操作符的类型…

实验五 图像增强—空域滤波

一、实验目的 了解图像平滑滤波器&#xff08;均值滤波和中值滤波&#xff09;和图像锐化算子&#xff08;Sobel算子、Prewitt算子、Laplacian算子&#xff09;在工程领域中的应用&#xff1b;理解图像平滑滤波器和图像锐化算子的工程应用范围&#xff1b;掌握图像平滑滤波器和…

[终端安全]-4 移动终端之硬件架构安全

1 移动终端硬件架构 上图图展示了典型移动终端硬件架构&#xff0c;包括应用处理器&#xff08;AP&#xff09;、基带处理器&#xff08;BP&#xff09;以及各类共享组件和外设&#xff0c;所有组件通过AXI总线&#xff08;和APB桥&#xff09;连接在一起。以下分别介绍基于整…

【Linux进阶】文件系统6——理解文件操作

目录 1.文件的读取 1.1.目录 1.2.文件 1.3.目录树读取 1.4.文件系统大小与磁盘读取性能 2.增添文件 2.1.数据的不一致&#xff08;Inconsistent&#xff09;状态 2.2.日志式文件系统&#xff08;Journaling filesystem&#xff09; 3.Linux文件系统的运行 4、文件的删…

69.WEB渗透测试-信息收集- WAF、框架组件识别(9)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;68.WEB渗透测试-信息收集- WAF、框架组件识别&#xff08;8&#xff09; 有无waf存在&am…

秋招提前批面试经验分享(下)

⭐️感谢点开文章&#x1f44b;&#xff0c;欢迎来到我的微信公众号&#xff01;我是恒心&#x1f60a; 一位热爱技术分享的博主。如果觉得本文能帮到您&#xff0c;劳烦点个赞、在看支持一下哈&#x1f44d;&#xff01; ⭐️我叫恒心&#xff0c;一名喜欢书写博客的研究生在读…

【MySQL】4.MySQL 的数据类型

MySQL 的数据类型 一.数据类型分类在这里插入图片描述二.注意点1.char VS varchar2.datetime VS timestamp3.enum 和 set 的使用方法 一.数据类型分类 二.注意点 1.char VS varchar char 的意义是直接开辟固定大小的空间&#xff0c;浪费磁盘空间&#xff0c;但是效率高varcha…

解决GPT-4o耗电难题!DeepMind新算法训练效率提升13倍,能耗降低10倍!

目录 01 有更好的解决方案吗&#xff1f; 02 从“超级batch”中筛选数据 03 技术介绍 04 实验结果 生成可学习batch 谷歌DeepMind推出的新算法JEST&#xff0c;将LLM训练的迭代次数减少了13倍&#xff0c;计算量降低了10倍&#xff0c;有望重塑AI未来。GPT-4o早已成为耗能…

下载,连接mysql数据库驱动(最详细)

前言 本篇博客&#xff0c;我讲讲如何连接数据库&#xff1f;我使用mysql数据库举例。 目录 下载对应的数据库jar 包 百度网盘 存有8.4.0版本压缩包&#xff1a;链接&#xff1a;https://pan.baidu.com/s/13uZtXRmuewHRbXaaCU0Xsw?pwduipy 提取码&#xff1a;uipy 复制这…

使用Ubuntu 22.04安装Frappe-Bench【二】

系列文章目录 第一章 使用VMware创建Ubuntu 22.04【一】 文章目录 系列文章目录前言什么是Frappe-Bench&#xff1f;使用安装ERPNext能实现什么效果&#xff1f; 官网给了一个说明 一、使用Ubuntu 22.04安装Frappe-Bench一、安装要求二、安装命令三、 可能出现问题 总结 前言 …

GESP C++一级真题

PDF图片1-7 点赞❤️关注&#x1f60d;收藏⭐️ 互粉必回&#x1f64f;&#x1f64f;&#x1f64f;

对BSV区块链的曼达拉网络通俗易懂的解释

​​发表时间&#xff1a;2023年6月15日 BSV区块链正在引入“曼达拉”升级&#xff0c;使BSV区块链网络的拓扑结构能够适配Teranode&#xff0c;适配这个可以大幅扩容的节点软件。BSV区块链上曼达拉网络的概念并不会改变整个系统的核心规则&#xff1b;相反&#xff0c;它能够引…

基于SSM+JSP的KTV点歌系统(带1w+文档)

基于SSMJSP的KTV点歌系统(带1w文档) 开发一个KTV点歌系统可以解决不利于线下点歌的问题&#xff0c;同时管理员可以利用网络对KTV点歌系统信息进行管理&#xff0c;设计的网站保证信息的完整安全&#xff0c;这样才能提高工作效率&#xff0c;保证系统安全正常的运行。 项目简介…

公众号文章阅读20w+?你猜腾讯给了我多少钱?

前两天写的一篇文章&#xff0c; 《1000T的文件怎么能快速从南京传到北京&#xff1f;最佳方案你肯定想不到》 一不小心被平台推荐&#xff0c;阅读量居然达到了20w&#xff08;这篇收益在文章底部&#xff01;&#xff09;。 留言也是相当精彩 说来惭愧&#xff0c;这篇文章我…

【网络安全】实验三(基于Windows部署CA)

一、配置环境 打开两台虚拟机&#xff0c;并参照下图&#xff0c;搭建网络拓扑环境&#xff0c;要求两台虚拟的IP地址要按照图中的标识进行设置&#xff0c;并根据搭建完成情况&#xff0c;勾选对应选项。注&#xff1a;此处的学号本人学号的最后两位数字&#xff0c;1学号100…

Nestjs基础

一、创建项目 1、创建 安装 Nest CLI&#xff08;只需要安装一次&#xff09; npm i -g nestjs/cli 进入要创建项目的目录&#xff0c;使用 Nest CLI 创建项目 nest new 项目名 运行项目 npm run start 开发环境下运行&#xff0c;自动刷新服务 npm run start:dev 2、…

【Unity】RPG2D龙城纷争(八)寻路系统

更新日期&#xff1a;2024年7月4日。 项目源码&#xff1a;第五章发布&#xff08;正式开始游戏逻辑的章节&#xff09; 索引 简介一、寻路系统二、寻路规则&#xff08;角色移动&#xff09;三、寻路规则&#xff08;角色攻击&#xff09;四、角色移动寻路1.自定义寻路规则2.寻…

基于ROS的智能网联车远程交互软件,全UI无需记忆指令,剑指核心原理。

基于ROS的智能网联车远程交互软件&#xff0c;全UI无需记忆指令&#xff0c;剑指核心原理。 服务于中汽恒泰&#xff0c;伟大的项目&#xff0c;希望看官点赞&#xff0c;谢谢~~ 进程&#xff08;节点&#xff09;列表化&#xff0c;参数面板化&#xff0c;实现快速机器人配置…

52-5 内网代理2 - LCX端口转发(不推荐使用LCX)

环境搭建: 本地开3台虚拟机:kali(必须)、windows2012与2008 (可换成其他windows虚拟机) kali - 网络配置成桥接模式 windows2012 - 设置两个网卡,NAT与桥接模式 注意:windows2012要关闭防火墙,要不然其他主机ping不通 关闭防火墙后再开启远程桌面连接 windwos20…