ThreadLocal InheritableThreadLocal TransmittableThreadLocal的使用以及原理

ThreadLocal

每个线程向ThreadLocal设置值,再取值,实现线程之间的隔离

public class ThreadLocalCase1 {
    
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    
    public static void main(String[] args) {
        Random random = new Random(); 
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                int value = random.nextInt(10000);
                threadLocal.set(value);
                System.out.println(Thread.currentThread().getName() + "开始执行,放入值,值为 : " + value);
                System.out.println(Thread.currentThread().getName() + "结束执行,进行取值,值为 : " + threadLocal.get());
            }); 
            thread.setName("thread-" + i); 
            thread.start();
        }
    }
}

结果

thread-0开始执行,放入值,值为 : 7406
thread-4开始执行,放入值,值为 : 5258
thread-3开始执行,放入值,值为 : 9672
thread-2开始执行,放入值,值为 : 8583
thread-1开始执行,放入值,值为 : 9311
thread-2结束执行,进行取值,值为 : 8583
thread-3结束执行,进行取值,值为 : 9672
thread-4结束执行,进行取值,值为 : 5258
thread-0结束执行,进行取值,值为 : 7406
thread-1结束执行,进行取值,值为 : 9311

实现了线程之间的隔离性

主线程向ThreadLocal设置值,每个子线程再取值

public class ThreadLocalCase2 {
    
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
      
    public static void main(String[] args) {
        Random random = new Random();
        int value = random.nextInt(10000);
        threadLocal.set(value);
        System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);
        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());
        });
        thread.setName("thread-1");
        thread.start();
        System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());
    }
}

结果

main放入值,值为 : 3831
main进行取值,值为 : 3831
thread-1进行取值,值为 : null

发现主线程向ThreadLocal设置值,每个子线程再取值时为null,这时需要换用InheritableThreadLocal

InheritableThreadLocal

主线程向InheritableThreadLocal设置值,子线程再取值

public class ThreadLocalCase2 {
    
    private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
    
    public static void main(String[] args) {
        Random random = new Random();
        int value = random.nextInt(10000);
        threadLocal.set(value);
        System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);
        Thread thread = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());
        });
        thread.setName("thread-1");
        thread.start();
        System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());
    }
}

结果

main放入值,值为 : 2046
main进行取值,值为 : 2046
thread-1进行取值,值为 : 2046
public class ThreadLocalCase3 {
    private static ExecutorService executor = Executors.newFixedThreadPool(2);
    private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Random random = new Random();
            int value = random.nextInt(10000);
            threadLocal.set(value);
            System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);
            executor.execute(() -> {
                System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());
            });
            System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());
            threadLocal.remove();
        }
    }
}

结果

main放入值,值为 : 8525
main进行取值,值为 : 8525
main放入值,值为 : 7802
main进行取值,值为 : 7802
pool-1-thread-1进行取值,值为 : 8525
main放入值,值为 : 3570
pool-1-thread-2进行取值,值为 : 7802
main进行取值,值为 : 3570
pool-1-thread-1进行取值,值为 : 8525
main放入值,值为 : 5081
main进行取值,值为 : 5081
pool-1-thread-2进行取值,值为 : 7802
main放入值,值为 : 4829
main进行取值,值为 : 4829
pool-1-thread-1进行取值,值为 : 8525

结果发现线程池取出了两次7802和三次8525,主线程中设置的3570、5081、4829在线程池中没有被取出,发生了错误。
这是由于InheritableThreadLocal会保证子线程能读取父线程中的数据,但线程池中的核心线程是复用的,所以有可能会发生重复读取的情况。

TransmittableThreadLocal

开启循环,每个循环中主线程使用InheritableThreadLocal进行设置,使用线程池来进行取值,解决线程复用产生的问题

public class ThreadLocalCase4 {
    
    private static ExecutorService executor = Executors.newFixedThreadPool(2);
    
    private static TransmittableThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<>();
    
    public static void main(String[] args) {
        
        for (int i = 0; i < 5; i++) {
            Random random = new Random();
            int value = random.nextInt(10000);
            threadLocal.set(value);
            System.out.println(Thread.currentThread().getName() + "放入值,值为 : " + value);
            executor.execute(TtlRunnable.get(() -> {
                System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());
            }));
            System.out.println(Thread.currentThread().getName() + "进行取值,值为 : " + threadLocal.get());
            threadLocal.remove();
        }
        
    }
}

结果

main放入值,值为 : 974
main进行取值,值为 : 974
main放入值,值为 : 4545
main进行取值,值为 : 4545
main放入值,值为 : 5901
pool-1-thread-1进行取值,值为 : 974
main进行取值,值为 : 5901
pool-1-thread-1进行取值,值为 : 5901
pool-1-thread-2进行取值,值为 : 4545
main放入值,值为 : 3716
main进行取值,值为 : 3716
pool-1-thread-1进行取值,值为 : 3716
main放入值,值为 : 2452
main进行取值,值为 : 2452
pool-1-thread-2进行取值,值为 : 2452

结果是即使线程池的线程被复用,读取的结果也是正常的

ThreadLocal原理

  • Thread
public class Thread implements Runnable {
   //省略....
   
    /* 
     * 当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal
     */
    ThreadLocal.ThreadLocalMap threadLocals = null;

   //省略....
}
  • ThreadLocal类:
    • ThreadLocal是一个泛型类,用于存储每个线程的本地变量副本。
      每个线程通过ThreadLocal实例获取和操作自己的变量副本,避免了多线程间的资源竞争。
  • ThreadLocalMap:
    • ThreadLocalMapThreadLocal的内部静态类,用于存储线程局部变量。
      每个线程都有一个独立的ThreadLocalMap实例,用来存储该线程的ThreadLocal变量。
      ThreadLocalMapThreadLocal实例作为键,实际存储的值作为值。
  • get()和set()方法:
    • 使用ThreadLocal的get()方法,可以获取当前线程的ThreadLocal变量。
      使用ThreadLocal的set()方法,可以设置当前线程的ThreadLocal变量。
      get()和set()方法内部会调用Thread.currentThread()获取当前线程,然后在该线程的ThreadLocalMap中查找或设置对应的值。
      在这里插入图片描述

内存泄露问题

  • ThreadLocalMap中的键使用弱引用:
    • ThreadLocalMap中的键是对ThreadLocal实例的弱引用。当没有强引用指向ThreadLocal实例时,垃圾回收器会回收这个ThreadLocal实例,导致ThreadLocalMap中的键变为null。
    • jdk1.8环境下的ThreadLocal采取嗅探机制,将调用get或set方法时,会主动探测是否含有key为空的value没有被回收的情况,如果有会主动清理。但我们依旧要在使用完后主动的调用remove
      在这里插入图片描述

InheritableThreadLocal原理

InheritableThreadLocal重写的方法

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    
    protected T childValue(T parentValue) {
        return parentValue;
    }

    
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

Thread结构

public class Thread implements Runnable {
    //省略....
    /* 
     * 存储本线程自身的ThreadLocal
     */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * 从父线程集成而来的ThreadLocalMap,
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
    //省略....
}

主线程中调用InheritableThreadLocal的set方法
set依旧是ThreadLocal

public void set(T value) {
    Thread t = Thread.currentThread();
    //被InheritableThreadLocal重写,第一次为空
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
    	//被InheritableThreadLocal重写,创建ThreadLocalMap赋值给inheritableThreadLocals变量
        createMap(t, value);
    }
}

这时就是Thread中的inheritableThreadLocals变量存储ThreadLocalMap

子线程初始化

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {

    //省略....
    
    //inheritThreadLocals为true
    //parent.inheritableThreadLocals就是在主线程进行set的时候生成为所以不为null
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

    //省略....        
    
}

ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)就是将主线程的ThreadLocalMap拷贝到子线程中

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}
/**
 * 将父线程的ThreadLocalMap拷贝到此线程中
 */
private ThreadLocalMap(ThreadLocalMap parentMap) {
    //父线程的ThreadLocalMap的entry数组 
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    // 这里的table就是此线程中的ThreadLocalMap的entry数组 
    table = new Entry[len];

    // 循环进行拷贝 parentMap 的记录
    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                //这里被InheritableThreadLocal重写,直接返回value,不做任何操作
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

到这里就将父线程中的值复制到子线程中了
执行原理图
在这里插入图片描述

TransmittableThreadLocal原理

知道了InheritableThreadLocal的原理后,TransmittableThreadLocal就比较好理解了,因为TransmittableThreadLocal继承了InheritableThreadLocal,所以是含有InheritableThreadLocal功能的,原理也比较简单,直接原理图就能概括
执行原理图
在这里插入图片描述

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

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

相关文章

浅析提高倾斜摄影超大场景的三维模型轻量化的数据质量关键技术

浅析提高倾斜摄影超大场景的三维模型轻量化的数据质量关键技术 倾斜摄影超大场景的三维模型轻量化的质量关键技术主要包括&#xff1a; 1、保持数据精度。在进行轻量化处理时&#xff0c;必须确保数据的精度不受损失&#xff0c;否则会影响后续分析和应用方案。因此&#xff0…

【Leetcode -剑指Offer 22.链表中倒数第k个结点 -203.移除链表元素】

Leetcode Leetcode -剑指Offer 22.链表中倒数第k个结点Leetcode -203.移除链表元素 Leetcode -剑指Offer 22.链表中倒数第k个结点 题目&#xff1a;输入一个链表&#xff0c;输出该链表中倒数第k个节点。为了符合大多数人的习惯&#xff0c;本题从1开始计数&#xff0c;即链表…

DAY829

学习目标&#xff1a;成就上瘾&#xff0c;学到欲罢不能 4月&#xff08;复习完高数18讲内容&#xff0c;背诵21篇短文&#xff0c;熟词僻义300词基础词&#xff09; 学习内容&#xff1a; 暴力英语&#xff1a;背单词150个&#xff0c;背《死亡诗社》经典语段&#xff0c;抄写…

【Spring Cloud】Spring Cloud 是什么?

文章目录 前言一、子项目二、常用组件三、把 Spring Cloud 官方、Netflix、Alibaba 三者整理成如下表格&#xff1a; 前言 Spring 以 Bean&#xff08;对象&#xff09; 为中心&#xff0c;提供 IOC、AOP 等功能。Spring Boot 以 Application&#xff08;应用&#xff09; 为中…

LightGBM面试题

1.偏差 vs 方差? 偏差是指由有所采样得到的大小为m的训练数据集&#xff0c;训练出的所有模型的输出的平均值和真实模型输出之间的偏差。 通常是由对学习算法做了错误的假设导致的描述模型输出结果的期望与样本真实结果的差距。分类器表达能力有限导致的系统性错误&#xff0c…

linux学习记录 和文件系统相关的命令

记录过程&#xff0c;会有错误,硬链接与软链接哪里可能没有说清楚 文件,目录操作命令 pwd 获取当前处于哪个目录当中&#xff0c;返回的是绝对路径 [rootlocalhost home]# pwd /homecd cd 相对/绝对路径 切换目录的&#xff0c;change directory .代表当前目录 …代表上一级…

基于opencv-python的深度学习模块案例

目录 图像分类 目标检测 人脸检测 姿态估计 车辆检测 一、图像分类 图像分类是基于深度学习的计算机视觉任务中最简单、也是最基础的一类&#xff0c;它其中用到的CNN特征提取技术也是目标检测、目标分割等视觉任务的基础。 具体到图像分类任务而言&#xff0c;其具体流…

PowerShell install go+caddy+filebrowser+nssm 实现部署文件系统

filebrowser filebrowser 是一个使用go语言编写的软件&#xff0c;功能是可以通过浏览器对服务器上的文件进行管理。可以是修改文件&#xff0c;或者是添加删除文件&#xff0c;甚至可以分享文件&#xff0c;是一个很棒的文件管理器&#xff0c;你甚至可以当成一个网盘来使用。…

光流法Optical Flow,Lucas-Kanade方法,CV中光流的约束分析

光流法Optical Flow&#xff0c;Lucas-Kanade方法&#xff0c;CV中光流的约束分析 Multiple View Geometry1. Optical Flow Estimation2. The Lucas-Kanade Method2.1 Brightness Constancy Assumption2.2 Constant motion in a neighborhood2.3 Compute the velocity vector2.…

excel数据分析比赛

基础 sql:百度网盘 请输入提取码 excel函数 <

什么是VBST和PVST?两者有啥区别?

在计算机网络中&#xff0c;VLAN&#xff08;Virtual Local Area Network&#xff0c;虚拟局域网&#xff09;是一种将局域网划分为多个逻辑上独立的子网的技术&#xff0c;它可以帮助网络管理员更好地管理网络资源。 在VLAN技术中&#xff0c;STP&#xff08;Spanning Tree P…

生成树协议三姐妹:STP、RSTP 和 MSTP,附思科和华为双厂商命令示例

在计算机网络中&#xff0c;为了保证网络拓扑结构的稳定性和可靠性&#xff0c;需要采用一些协议进行网络的管理和控制。其中&#xff0c;STP、RSTP 和 MSTP 是三种常用的网络管理协议。本文将分别介绍这三种协议&#xff0c;并且使用华为、思科两家厂商作为案例给出相应的命令…

全网抓包天花板教程,CSDN讲的最详细的Fiddler抓包教程。2小时包你学会

目录 前言 一、安装 Fiddler 二、打开 Fiddler 并设置代理 三、抓取 HTTP/HTTPS 流量 四、流量分析和调试 五、应用场景 六、注意事项 七、实际案例 八、拓展阅读 九、结论 前言 Fiddler 是一款功能强大的网络调试工具&#xff0c;可以用于捕获和分析 HTTP 和 HTTPS …

QT QPainter 绘制基本图形元件简介

1.基本图形元件 QPainter 提供了很多绘制基本图形的功能&#xff0c;包括点、直线、椭圆、矩形、曲线等&#xff0c;由这些基本的图形可以构成复杂的图形。QPainter 中提供的绘制基本图元的函数如下表所示。每个函数基本上都有多种参数形式&#xff0c;这里只列出函数名&#x…

微信小程序php+vue 校园租房指南房屋租赁系统

本着诚信的原则&#xff0c;平台必须要掌握出租方必要的真实可信的信息&#xff0c;这样就可以防止欺诈事件的发生&#xff0c;事后也可以联系找到出租方。并且租金等各方面规范标准化&#xff0c;在这易租房诚信可信的平台让承租方与出租方充分有效对接&#xff0c;既方便了承…

ConcurrentHashMap是如何保证线程安全的

ConcurrentHashMap是如何保证线程安全的 定义和问题解决JDK 1.7实现原理JDK 1.8性能优化总结 定义和问题解决 ConcurrentHashMap相当于HashMap的多线程版本。 它的功能本质上和HashMap没有什么区别&#xff0c;因为HashMap在并发操作的时候会出现各种问题&#xff0c;比如&am…

YOLOv1代码复现1:辅助功能实现

YOLOv1代码复现1&#xff1a;辅助功能实现 前言 ​ 在经历了Faster-RCNN代码解读的摧残后&#xff0c;下决心要搞点简单的&#xff0c;于是便有了本系列的博客。如果你苦于没有博客详细告诉你如何自己去实现YOLOv1&#xff0c;那么可以看看本系列的博客&#xff0c;也许可以帮助…

大屏开发需要知道哪些知识

大屏 大屏是什么呢&#xff1f;再我前几年刚接触这个词得时候很新颖&#xff0c;全名叫态势感知大屏&#xff0c;大屏得特点是炫酷、好看&#xff0c;给用户满满得科技感。 听一位前辈说当年再招标会上&#xff0c;再都用exel、word做界面图表文档得时候&#xff0c;有一家公司…

打包后dist包中app.**.js文件暴露大量接口信息,webpack-obfuscator对打包后的js代码混淆加密

问题描述 打包后dist包中app.**.js文件暴露大量接口信息&#xff0c;而webpack-obfuscator可以对打包后的js代码混淆加密 版本信息 webpack: 4.x.x node: 14.18.0 webpack4环境下使用webpack-obfuscator不能使用最新版本 我的下载版本是&#xff1a; npm install --save-de…

玩转ChatGPT:论文翻译润色

一、写在前面 首先还是让小Chat推销下自己&#xff1a; 嘿&#xff01;你是否在写论文的过程中感到头疼&#xff0c;无从下手&#xff1f;你是否在担心自己的语言表达不够专业、不够流畅&#xff0c;影响了论文的质量&#xff1f;不要担心&#xff0c;ChatGPT的润色服务可以帮…