用ThreadLocal解决线程隔离问题

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

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.mfbz.cn/a/782177.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

瑞芯微rk356x TF卡烧写选择指定的屏幕打印烧写的过程

rk356x中TF卡烧写屏幕选择 1、开发环境2、问题描述3、解决办法4、总结5、 图片展示1、开发环境 系统:linux系统 芯片:356x 显示:多屏显示(HDMI, MIPI, LVDS, EDP) 2、问题描述 由于在多屏显示的情况下,HDMI屏在LVDS、MIPI或者EDP协同下,默认情况下,在TF卡烧录过程中…

论文润色最强最实用ChatGPT提示词指令

大家好&#xff0c;感谢关注。我是七哥&#xff0c;一个在高校里不务正业&#xff0c;折腾学术科研AI实操的学术人。关于使用ChatGPT等AI学术科研的相关问题可以和作者七哥&#xff08;yida985&#xff09;交流&#xff0c;多多交流&#xff0c;相互成就&#xff0c;共同进步&a…

C++语言相关的常见面试题目(二)

1.vector底层实现原理 以下是 std::vector 的一般底层实现原理&#xff1a; 内存分配&#xff1a;当创建一个 std::vector 对象时&#xff0c;会分配一块初始大小的连续内存空间来存储元素。这个大小通常会随着 push_back() 操作而动态增加。 容量和大小&#xff1a;std::vec…

【Linux】进程间的通信----管道

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

妈妈带女儿美在心里

在这个充满温情与惊喜的午后&#xff0c;阳光温柔地洒落在每一个角落&#xff0c;仿佛连空气弥漫着幸福的味道。就在这样一个平凡的时刻&#xff0c;一段关于爱与成长的温馨画面&#xff0c;悄然在网络上绽放&#xff0c;引爆了无数人的心弦——#奚梦瑶2岁女儿身高#&#xff0c…

在 VS Code 中自动化 Xcode 项目编译和调试

在 VS Code 中自动化 Xcode 项目编译和调试 在日常的开发工作中&#xff0c;Xcode 是 macOS、iOS、watchOS 和 tvOS 应用程序开发的主要工具。为了提高工作效率&#xff0c;许多开发者选择在 Visual Studio Code (VS Code) 中编辑代码&#xff0c;并希望能够直接从 VS Code 启…

【vue组件库搭建06】组件库构建及npm发包

一、格式化目录结构 根据以下图片搭建组件库目录 index.js作为入口文件&#xff0c;将所有组件引入&#xff0c;并注册组件名称 import { EButton } from "./Button"; export * from "./Button"; import { ECard } from "./Card"; export * fr…

网络通信总体框架

目录 网络通信 一、网络通信的定义与基本原理 二、网络通信的组成要素 三、网络通信的应用与发展 网络体系结构 一、网络体系结构的定义与功能 二、OSI七层参考模型 三、网络体系结构的重要性 网络核心与边缘 一、网络核心 1. 定义与功能 2. 组成部分 3. 技术特点 …

昇思25天学习打卡营第19天|LSTM+CRF序列标注

概述 序列标注指给定输入序列&#xff0c;给序列中每个Token进行标注标签的过程。序列标注问题通常用于从文本中进行信息抽取&#xff0c;包括分词(Word Segmentation)、词性标注(Position Tagging)、命名实体识别(Named Entity Recognition, NER)等。 条件随机场&#xff08…

01:spring

文章目录 一&#xff1a;常见面试题1&#xff1a;什么是Spring框架&#xff1f;1.1&#xff1a;spring官网中文1.2&#xff1a;spring官网英文 2&#xff1a;谈谈自己对于Spring IOC和AOP的理解2.1&#xff1a;IOCSpring Bean 的生命周期主要包括以下步骤&#xff1a; 2.2&…

国产化新标杆:TiDB 助力广发银行新一代总账系统投产上线

随着全球金融市场的快速发展和数字化转型的深入推进&#xff0c;金融科技已成为推动银行业创新的核心力量。特别是在当前复杂多变的经济环境下&#xff0c;银行业务的高效运作和风险管理能力显得尤为重要。总账系统作为银行会计信息系统的核心&#xff0c;承载着记录、处理和汇…

MySQL-行级锁(行锁、间隙锁、临键锁)

文章目录 1、介绍2、查看意向锁及行锁的加锁情况3、行锁的演示3.1、普通的select语句&#xff0c;执行时&#xff0c;不会加锁3.2、select * from stu where id 1 lock in share mode;3.3、共享锁与共享锁之间兼容。3.4、共享锁与排他锁之间互斥。3.5、排它锁与排他锁之间互斥3…

离线开发(VSCode、Chrome、Element)

一、VSCode 扩展 使用能联网的电脑 A&#xff0c;在VSCode官网下载安装包 使用能联网的电脑 A&#xff0c;从扩展下载vsix扩展文件 将VSCode安装包和vsix扩展文件通过手段&#xff08;u盘&#xff0c;刻盘 等&#xff09;导入到不能联网的离线电脑 B 中 在离线电脑 B 中安装…

计算机网络之无线局域网

1.无线局域网工作方式 工作方式&#xff1a;每台PC机上有一个无线收发机&#xff08;无线网卡&#xff09;&#xff0c; 它能够向网络上的其他PC机发送和接受无线电信号。 与有线以太网相似&#xff0c;无线局域网也是打包方式发送数据的。每块网卡都有一个永久的、唯一的ID号…

springboot配置扫描生效顺序

文章目录 举例分析项目结构如下noddles-user-backend 两个配置文件noddles-user-job 配置文件noddles-user-server 配置文件问题:server和Job启动时对应加载的数据库配置为哪一个&#xff1f; 总结 在微服务架构中&#xff0c;backend模块会定义一个基础的配置文件&#xff0c;…

java集合(2)

目录 一. Map接口下的实现类 1. HashMap 1.1 HashMap常用方法 2. TreeMap 2.1 TreeMap常用方法 3. Hashtable 3.1 Hashtable常用方法 4.Map集合的遍历 4.1 根据键找值 4.2 利用map中的entrySet()方法 二.Collections类 1.Collections类中的常用方法 三. 泛型 1. 为什…

运维锅总详解系统启动流程

本文详细介绍Linux及Windows系统启动流程&#xff0c;并分析了它们启动流程的异同以及造成这种异同的原因。希望本文对您理解系统的基本启动流程有所帮助&#xff01; 一、Linux系统启动流程 Linux 系统的启动流程可以分为几个主要阶段&#xff0c;从电源开启到用户登录。每个…

揭秘IP:从虚拟地址到现实世界的精准定位

1.IP地址介绍 1.内网 IP 地址&#xff08;私有 IP 地址&#xff09; 内网 IP 地址&#xff0c;即私有 IP 地址&#xff0c;是在局域网&#xff08;LAN&#xff09;内部使用的 IP 地址。这些地址不会在公共互联网中路由&#xff0c;因此可以在多个局域网中重复使用。私有 IP 地…

设计模式探索:责任链模式

1. 什么是责任链模式 责任链模式 (Chain of Responsibility Pattern) 是一种行为型设计模式。定义如下&#xff1a; 避免将一个请求的发送者与接收者耦合在一起&#xff0c;让多个对象都有机会处理请求。将接收请求的对象连接成一条链&#xff0c;并且沿着这条链传递请求&…

14-43 剑和诗人17 - ActiveRAG之主动学习

​​​​​ 大型语言模型 (LLM) 的出现开启了对话式 AI 的新时代。这些模型可以生成非常像人类的文本&#xff0c;并且比以往更好地进行对话。然而&#xff0c;它们在仅依赖预训练知识方面仍然面临限制。为了提高推理能力和准确性&#xff0c;LLM 需要能够整合外部知识。 检索…