ThreadLocal InheritableThreadLocal TransmittableThreadLocal的使用以及原理

news2024/11/19 2:23:21

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

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

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

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

相关文章

Postman接口与压力测试实例

Postman是一款功能强大的网页调试与发送网页HTTP请求的Chrome插件。它提供功能强大的 Web API & HTTP 请求调试。 1、环境变量和全局变量设置 环境变量可以使用在以下地方&#xff1a; URLURL paramsHeader valuesform-data/url-encoded valuesRaw body contentHelper f…

MySQL学习笔记第一天

第02章 MySQL环境搭建 1.MySQL的卸载 步骤1&#xff1a;停止MySQL服务 在卸载之前&#xff0c;先停止MySQL8.0的服务。按键盘上的“Ctrl Alt Delete”组合键&#xff0c;打开“任务管理器”对话框&#xff0c;可以在“服务”列表找到“MySQL8.0”的服务&#xff0c;如果现…

AIGC: Midjourney和Stable Diffusion在大厂中的应用

AIGC: Midjourney和Stable Diffusion在大厂中的应用和教程​http://www.webhub123.com/#/home/detail?projectHashid51631966&ownerUserid21336964 收录效果如下 登录后即可一键拥挤收藏以下所有文章网址到我的收藏夹&#xff0c;网站帮你简单高效地管理你的网络收藏&…

Memtiter-benchmark源码解析4memtier_benchmark.cpp源代码解读

run_benchmark 程序入口 First , create the cg_thread instances in the vector threads and then call each cg_thread’s prepare() function of them. Subsequently, launch each thread in the vector threads cg_thread m_base event_base_new() // line 528 一个cli…

移动端手机网页适配iPad与折叠屏设备

采用的网页适配方案&#xff1a;移动端页面px布局适配方案&#xff08;viewport&#xff09; 产生此问题的原因 由于手机与平板等设备宽高比差异导致页面展示不全或者功能按钮展示在视口之外点击不到。 简单来说就是我们的页面都是瘦长(即高大于宽)的&#xff0c;而折叠屏等设…

【计算机专业应届生先找培训还是先找个工作过渡一下?】

计算机专业应届生先找培训还是先找个工作过渡一下&#xff1f; 计算机应届生是先培训还是先工作&#xff0c;这个问题应该困扰了很多专业技能一般的同学&#xff0c;尤其是学历方面还没有优势的普通本专科院校。都说技术与学历优秀的人进大厂&#xff0c;技术一般学历优秀的人能…

@ComponentScan自动扫描组件并指定扫描规则

1.使用注解配置包扫描 1.1.创建相关类 分别创建BookDao、BookService、BookServiceImpl以及BookController这三个类&#xff0c;并在这三个类中分别添加Repository、Service、Controller注解 BookDaopackage com.tianxia.springannotation.dao;import org.springframework.s…

【社区图书馆】如何唤醒数学脑

如何唤醒数学脑 内容简介 每个人天生都有数学力&#xff0c;有着内建的“数学式思维模式”&#xff0c;若能有效发挥&#xff0c;就能在学校、职场、人际关系中表现出来&#xff0c;从容不迫地获得更好的效率及成就感。 但这种思维模式会受到周围情境、心理状态等因素的影响&…

思必驰闯关科创板:对标科大讯飞,发展阶段落后逾10年

4月17日&#xff0c;人机对话解决方案提供商——思必驰科技股份有限公司&#xff08;下称“思必驰”&#xff09;已更新提交相关财务资料&#xff0c;根据相关规定&#xff0c;上交所恢复了其发行上市审核。 2022年7月15日&#xff0c;思必驰向科创板递交IPO申请。今年3月底&am…

贵金属期货交易平台排行榜是真的还是假的?如何选择?

贵金属期货交易是一种非常常见的投资形式&#xff0c;其市场容量非常大&#xff0c;因此有各种各样的期货交易平台。这导致许多投资者不知道如何选择这些平台。对于投资者来说&#xff0c;只有选择一个高质量的期货交易平台&#xff0c;他们才能更好地进行后续的交易。如何选择…

BloomFilter在Drois中的应用

1.简介&#xff1a; BloomFilter是一种多哈希函数映射的快速查找算法&#xff0c;它实际上是由一个超长的二进制位数组和一系列的哈希函数组成的。初始时二进制位数组全部为0&#xff0c;当给定一个待查询的元素时&#xff0c;这个元素会被一系列哈希函数计算映射出一系列的值…

【redis】布隆过滤器BloomFitter

【redis】布隆过滤器BloomFitter 文章目录 【redis】布隆过滤器BloomFitter前言一、面试题二、是什么1、设计思想&#xff1a; 本质就是判断具体数据是否存在于一个大的集合当中 三、布隆过滤器特点1、重点 有&#xff0c;是可能有&#xff0c;无是一定无2、小总结 四、原理1、…

Node【包】

文章目录 &#x1f31f;前言&#x1f31f;Nodejs包&#x1f31f;什么是包&#xff1f;&#x1f31f;自定义包&#x1f31f;包配置文件&#x1f31f;示例&#x1f31f;Package.json 属性说明&#x1f31f;语义化版本号&#x1f31f;package.json示例 &#x1f31f;符合CommonJS规…

怎么做好管综深化设计?高效就靠它

管线综合深化设计是指将设计阶段完成的机电管线进一步深化排布&#xff0c;结合建筑装修的要求&#xff0c;根据管线不同的性质与功能统筹管线位置排布。管线综合是 BIM 技术现阶段最基础也是应用最广泛的点&#xff0c;如何使机电各系统的使用功能效果达到最佳&#xff0c;整体…

(排序11)排序的时间复杂度,空间复杂度,稳定性总结

图片总结 内排序时间复杂度总结 内部排序&#xff1a;数据元素全部放在内存中的排序。. 在内排序当中比较快的有希尔排序&#xff0c;堆排序&#xff0c;快速排序&#xff0c;归并排序&#xff0c;这四个排序的时间复杂度都是O(n*logn)。其中希尔排序的时间复杂度更加准确的来…

RB-PEG-COOH,罗丹明聚乙二醇羧基;RhodamineB-PEG-acid;RB-PEG2000-COOH

RB-PG-COOH, 罗丹明聚乙二醇羧基 中文名称&#xff1a;罗丹明-聚乙二醇-羧基 英文名称&#xff1a;RhodamineB-PEG-acid RB-PEG-COOH 性状&#xff1a;固体或粘性液体&#xff0c;取决于分子量 溶剂&#xff1a;溶于水和DCM、DMF、DMSO等常规性有机溶剂 分子量&#xff1…

MySQL运维28-MySQL复制

文章目录 1、MySQL复制模式2、MySQL复制的兼容性3、与MySQL复制相关的两种日志3.1、中继日志3.2、MySQL复制的状态日志 4、MySQL主从复制的实施示例4.1、主从库安装4.2、主库配置4.3、从库的配置4.4、启动复制和确认成功 5、对MySQL复制的监控6、MySQL监控的运维6.1、在从库修改…

ASEMI代理ADAU1979WBCPZ原装ADI车规级ADAU1979WBCPZ

编辑&#xff1a;ll ASEMI代理ADAU1979WBCPZ原装ADI车规级ADAU1979WBCPZ 型号&#xff1a;ADAU1979WBCPZ 品牌&#xff1a;ADI /亚德诺 封装&#xff1a;LFCSP-40 批号&#xff1a;2023 安装类型&#xff1a;表面贴装型 引脚数量&#xff1a;40 类型&#xff1a;车规级…

Linux串口应用编程

在Linux系统中&#xff0c;操作设备的统一接口就是&#xff1a;open/ioctl/read/write。 对于UART&#xff0c;又在ioctl之上封装了很多函数&#xff0c;主要是用来设置行规程。 所以对于UART&#xff0c;编程的套路就是&#xff1a; 使用open函数打开串口设置行规程&#xff…

vue---自定义指令

目录 1、为什么使用自定义指令&#xff1f; 2、自定义指令 3、自定义指令使用 1、为什么使用自定义指令&#xff1f; 什么时候我们需要去自定义一个指令呢&#xff1f; 事件修饰符&#xff0c;很大的程度上我们是为了让我们的代码更加显得是数据驱动的以及可测试的&#x…