ThreadLocal失效

2023-10-30

         在JDK中,解决线程冲突问题,有两种解决方案:l  给临界区加锁;l  本地化临界区。

第一种解决方案的典型代表是Synchonized。第二种的典型代表是ThreadLocal。而CopyOnWrite是这两种方案的融合。

         ThreadLocal为每个线程的并发访问数据创建一个副本,通过对副本的操作来隔离临界区的污染。虽然增加了内存空间的消耗,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

         当然ThreadLocal不能完全解决并发的问题,因为它只是隔离了临界区,抛开了临界区的同步问题,导致多个副本的存在。所以使用的时候要因地制宜,符合自身的逻辑。

        

Demo

         ThreadLocal的使用很简单,如下所示,先声明一个ThreadLocal的私有变量。然后在线程启动的时候赋值set(),在使用的时候再get()出来。

private ThreadLocal local = new ThreadLocal();
public void run() {
    local.set(objLocal);
    // ......
    local.get();

         也可以通过覆盖初始值的方法initialValue()来实现赋值。

private ThreadLocal local = new ThreadLocal() {
    @Override
    protected ObjectinitialValue() {
        // ......
        return XXX;
    }
};


 

Code

         那ThreadLocal是如何能实现本地化临界区的功能呢?我们到JDK一看究竟。

         在线程类Thread中,通过一个Map属性threadLocals来存放临界区的数据。我暂且称之为本地数据区。   这个Map的keyThreadLocal实例,value为临界区的数据值

         这个ThreadLocalMap没有实现Map接口,但是也用到了散列表来组织数据。

    ThreadLocal.ThreadLocalMap threadLocals = null;

         接着,我们看到ThreadLocal取值get()时候,需要传入当前线程t来获取本地数据区t.threadLocals。获取到数据区后,使用ThreadLocal实例作为key来取值。

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

         同样,赋值set()也是差不多的逻辑。如果t.threadLocals不存在就新建一个ThreadLocalMap对象。

         这里有个需要注意的点,获取本地数据区是要传入当前线程的,因为threadLocals是挂在具体的Thread对象上。所以set()需要在线程运行的时候进行,否则就会导致传入主线程,取到了主线程的本地数据区。

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

         最后,我们发现ThreadLocal赋值到本地数据区的时候,会创建一个Entry来存放:keyThreadLocal实例,value为副本值。这里其实只是把value的值赋过来,没有做clone的处理。

         所以需要加倍注意的是,此处只是值拷贝,没做clone(深度拷贝)。如果传进来的是个引用,就不能做到隔离了。

    static class Entry extendsWeakReference<ThreadLocal> {
        Object value;
        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    }

P.S 隔离失效

         从上面ThreadLocal源码可以看出:

1)      ThreadLocal赋初始值的时候,需要在线程运行中,即run()中,否则ThreadLocalMap会挂错线程

2)      使用ThreadLocal隔离的值不能是引用,否则隔离的只是引用,而引用所指向的对象则隔离失败;

3)      本地数据区ThreadLocalMap是挂在Thread对象上的,所以要注意线程复用(线程池)所带来的污染

 

set引用

         下面程序先创建一个SHARE_LIST作为临界区,然后创建两个线程同时去做修改。

         给每个线程引入ThreadLocal属性试图本地化SHARE_LIST来隔离多线程的冲突。

         根据ThreadLocal的设计初衷,应该是在各个Thread创建自己的本地数据区,互不影响。后来却发现SHARE_LIST被污染了,所有线程的修改都写入了同一个SHARE_LIST。最后,主线程和另外两个线程输出的结果都是一样的。

         这里ThreadLocal只是本地化(隔离)了引用值,而没有本地化引用的对象本身,所以出现了这种现象。

    public static List<String>SHARE_LIST = new CopyOnWriteArrayList<String>();
    public static void main(String[]args) throws Exception {
        SHARE_LIST.add("Int");
        System.out.println("Change before : " + SHARE_LIST);
        new MyThread2("Tom").start();
        new MyThread2("Jack").start();
        System.out.println("Change after :");
 
        Thread.currentThread().sleep(500);
        System.out.println(Thread.currentThread() + ":" + SHARE_LIST);
    }
 
    static class MyThread2 extends Thread {
        privateThreadLocal<List<String>> local = new ThreadLocal<List<String>>();
        private String name;
 
        public MyThread2(Stringname) {
            this.name = name;
        }
 
        @Override
        public void run() {
            local.set(SHARE_LIST);
            local.get().add(name);   // Change the parameter locally.
            try {
                Thread.currentThread().sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + ":" + local.get());
        }
    }


挂错线程

         把上面的代码稍作修改,把ThreadLocal的赋初始值放在创建线程实例的构造函数中。执行程序后,发现获取本地数据区local.get()的时候抛出了NullPointerException。

         这是因为线程创建的时候还在主线程main中,这个时候赋值set()就会把数据放到了main的本地数据区;而到了子线程run()的时候,获取本地数据区get()取的是子线程的,所以就会抛空指针。

    public static void main(String[]args) throws InterruptedException {
        SHARE_LIST.add("Int");
        System.out.println("Change before : " + SHARE_LIST);
        new MyThread3("Tom", SHARE_LIST).start();
        new MyThread3("Jack", SHARE_LIST).start();
        System.out.println("Change after :");
 
        Thread.currentThread().sleep(500);
        System.out.println(Thread.currentThread() + ":" + SHARE_LIST);
    }
 
    static class MyThread3 extends Thread {
        privateThreadLocal<List<String>> local = new ThreadLocal<List<String>>();
        private String name;
 
        public MyThread3(String name, List<String>list) {
            this.name = name;
            local.set(list);
        }
 
        @Override
        public void run() {
            local.get().add(name); // Changethe parameter locally.
            try {
                Thread.currentThread().sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + ":" + local.get());
        }
    }


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ThreadLocal失效 的相关文章

  • <稀缺-我们是如何陷入贫穷与忙碌的> 摘要

    2015 07 08 10 12 lt 稀缺 我们是如何陷入贫穷与忙碌的 gt 摘要 这本书分析了稀缺的内在来源和所造成的影响 列举了许多例子 下面我把它的核心思想记录一下 稀缺心态是一切稀缺的根源 资源稀缺并不可怕 就怕有稀缺心态 以后简
  • c陷阱与缺陷

    第一章 词法陷阱 1 这一章没有太多 干货 唯一比较有趣的就是 1 3 语法分析中的 贪心法 所讲内容 这个 贪心 就是编译器会读入字符 如果能新读入的字符和之前所读入字符能组成符号 则编译器会继续读入下一个字符 直到读入的字符不能和之前的
  • 《大五人格心理学》读书笔记

    这本书介绍了一下职场中的大五人格 具有不同人格特质的人适合干不同的工作 了解自己的人格特质 有利于自己的职业规划 了解同事的人格特质 有利于合作 1 宜人性 宜人性的心声 这对他人有什么影响VS 这对我有什么价值 宜人性的子维度 同理心 经
  • 读书笔记:《人工智能》

    读书笔记 摘自 人工智能 作者 李开复 王咏刚 第一章 人工智能来了 人工智能已经来了 而且它就在我们身边 几乎无处不在 人类 你好 不管我们是碳基人类还是硅基机器人 都没有本质的区别 我们中的每一员都应获得应有的尊重 每当前沿科技取得重大
  • Java Maven安装及环境配置教程

    一 安装 1 安装包 apache maven 3 6 3 安装包下载地址 2 下载安装包然后直接解压就行 注意 文件的位置路径不能有中文 二 环境配置 1 用户变量 双击Path 点击新建 将如下复制进去 然后点击确定 MAVEN HOM
  • 202317读书笔记|《心寂犹似远山火:斋藤茂吉短歌300》——茫茫心海里,孤帆与谁同

    202317读书笔记 心寂犹似远山火 斋藤茂吉短歌300 茫茫心海里 孤帆与谁同 很高兴周五这一天 之前很粉俳句的时候订阅的书都在今天都上架了 可以一饱眼福了 短歌是日本和歌一种诗体 是由三十一音节组成的定型歌体 格式为 五七五七七 的排列
  • 《Android 开发艺术探索》笔记5--View工作原理

    View工作原理思维导图 ViewRoot和DecorView MeasureSpec 理解MeasureSpec MeasureSpec和LayoutParams关系 View的工作流程 measure过程 正确获取宽高方法 layout
  • 《曾国藩家书》读书手记(修身篇一)

    曾国藩被章太炎评价为 誉之则圣相 谳之则元凶 为什么有这样的评价呢 我们可以看出曾国藩这个人褒贬不一 不过毛和蒋对于曾国藩都是推崇备至 毛说过 吾近于人 独服于曾国藩 看来曾国藩还是有可取之处的 尤其是他的家书 很多人评价甚高 一 修身篇
  • 《Java 并发编程实战》--读书笔记

    Java 并发编程实战 注 极客时间 Java 并发编程实战 读书笔记 GitHub https github com ByrsH Reading notes blob master Concurrency Java并发编程实战 极客时间
  • 《Vision-Language Pre-Training with Triple Contrastive Learning》/《具有三重对比学习的视觉语言预训练》

    一 摘要 视觉语言表示学习很大程度上受益于通过对比损失 例如 InfoNCE损失 的图像 文本对齐 这种对齐策略能够最大化图像与其匹配文本之间的互信息 MI 然而 简单地执行跨模态对齐 CMA 不能确保来自相同模态的相似输入保持接近 这可能
  • 两个重要极限定理推导

    两个重要极限定理 lim x
  • 《从Paxos到ZooKeeper》读书笔记之第一章(二)

    从Paxos到ZooKeeper 读书笔记之第一章 二 1 2从ACID到CAP BASE 这一节由三小节 从大家数值的数据库事务的四个特性 引出来分布式事务的概念 通过对ACID模型的讨论 提出如何构建一个兼顾可用性和一致性的分布式系统方
  • 《Android 开发艺术探索》笔记2--IPC机制

    Android 开发艺术探索 笔记2 IPC机制 思维导图 Android IPC简介 Android中的多进程的模式 IPC基础概念 Serializable接口 Parcelable接口 Android的几种跨进程的方式 使用Bundl
  • 没有权限删除文件

    通过远程发版时 有可能会没有权限删除文件 如下解决方法 1 将user 用户切换root 用户 sudo su root 该方法不一定成功 因为有可能设置权限你不能切换 但成功以后一劳永逸 当方法1没有成功时 采用如下方法 2 将你所操作的
  • URI中的 “//” 有什么用

    前言 很多时候互联网很多东西都是很有意思的 比如 http 这个双斜杠 解释这个东西就需要翻墙去国外了 入口 在2009年10月 BBC中的一篇新闻讲述了 的用途 截图如下所示 文章翻译 互联网地址开头的大幅删减长期以来一直困扰着网民 现在
  • AI工具究竟是帮手还是对手?你怎么看,一起来聊聊吧!

    AI工具究竟是帮手还是对手 你怎么看 一起来聊聊吧 1 你现在正在哪个领域学习或工作呢 你用过哪些AI智能工具 2 作为行业人士或正在学习的学生 你认为AI工具的出现会提升你的工作或学习效率吗 3 对于AI智能工具的出现 我们应该做好哪些准
  • 警告: Unable to find required classes (javax.activation.DataHandler and javax.mail

    在调试Axis1 4访问WebService服务时 出现以下警告 警告 Unable to find required classes javax activation DataHandler and javax mail internet
  • 光栅尺的相关知识

    光栅尺的相关知识 1 光栅尺的原理 2 光栅尺的精度 3 光栅尺的制造 4 增量式光栅尺和绝对式光栅尺的区别 5 光栅尺的选择 6 光栅尺与编码器 1 光栅尺的原理 我们可以类比普通卷尺 尺子上面有刻度 以这些刻度作为基准 你只需要把要测的
  • 数仓相关知识点/笔记(OLTP和OLAP)

    现在实时数仓是一个非常火的趋势 最近开始逐渐了解一些数仓相关的东西 从基础的理论知识包括架构 算一个基础总结和学习记录吧 包括OLTP和OLAP 基础表和数据湖相关概念 不定期补充更新 联机事务处理OLTP和联机分析处理OLAP 关键词 日
  • 工业数据的特殊性和安全防护体系探索思考

    随着工业互联网的发展 工业企业在生产运营管理过程中会产生各式各样数据 主要有研发设计数据 用户数据 生产运营数据 物流供应链数据等等 这样就形成了工业大数据 这些数据需要依赖企业的网络环境和应用系统进行内外部流通才能实现价值挖掘 如何高效安

随机推荐

  • 试题 算法训练 最小距离

    试题 算法训练 最小距离 资源限制 时间限制 1 0s 内存限制 256 0MB 最小距离 问题描述 数轴上有n个数字 求最近的两个数 即min abs x y 输入格式 第一行包含一个整数n 接下来一行 表示n整数 输出格式 一个整数表示
  • 【ES6】学ES6一篇就够了

    ES6 let 声明变量 let 关键字声明变量是在 es6 中引入的 使用 let 声明变量主要有以下特点 使用 let 声明的变量具有块级作用域 使用 let 声明的变量没有变量提升 使用 let 声明的变量具有暂时性死区 let 声明
  • 编译原理书籍推荐

    大学课程为什么要开设编译原理呢 这门课程关注的是编译器方面的产生原理和技术问题 似乎和计算机的基础领域不沾边 可是编译原理却一直作为大学本科的必修课程 同时也成为了研究生入学考试的必考内容 编译原理及技术从本质上来讲就是一个算法问题而已 当
  • Unity3D 鼠标控制角色移动与奔跑

    最新补充 一般在做鼠标选择时是从摄像机向目标点发送一条射线 然后取得射线与对象相交的点来计算3D目标点 后来在开发中发现了一个问题 射线被别的对象挡住了 就是如果主角的前面有别的游戏对象挡着 此时如果使用射线的原理 鼠标选择被档的对象 这样
  • Java高效开发-远程debug

    1 前言 这怎么回事 在本地还好好 放到服务器就不行了 这该怎么排查 日志也看不出来啥呀 日常开发中经常会出现这种问题 这时候就可以尝试idea远程debug的模式试试 2 使用 1 环境 idea2021 2 idea配置 重点 将自动生
  • 解决Could not find artifact com.oracle:ojdbc7:pom:12.1.0.2 的方案

    1 试了很多方法 不行 2 最终方案 使用私服nexus解决 3 idea创建maven项目 引入lib文件夹 3 1 打包后上传到私服 坐标为
  • Windows10+Python3.6+创建虚拟环境+pycharm+mySQL+flask (一)

    1 安装Python3 6 首先下载python3 6 https www python org getit 在官网上 下下来之后可以在你的下载路径里面找到 我是64位的操作系统 双击安装 这里注意一下要选择 Add Python3 6 t
  • umi——02——mook和反向代理(跨域)

    1 测试mock的简单使用 首先我们在mock文件夹创建一个文件 文件名随便取 写上这样一段代码 代表Get请求 在登录组件Login中发起请求 启动项目 并在项目的url地址输入 users 就可以看到 2 登录案例 api js exp
  • Linux磁盘配额配置

    磁盘配额配置 1 理解磁盘配额的作用 2 掌握磁盘配额工具 3 掌握磁盘配额配置的方法 任务 账号为user 密码为123456的用户磁盘限额情况如下 user用户能够取得80KB的磁盘使用量 hard 文件数量为5个 只要容量使用超过30
  • java 文件下载进度条_下载文件时显示动态的进度条(前端easyUI,后台java)

    最近有点闲 我们的架构师让我在文件下载时显示进度条 咳咳 自从组里来了前端妹纸后 好久没写前端代码了 架构师推荐的用监听器 链接找不到了 实现得有点复杂 我没太看懂 继续百度 看到了 在下载时计算进度 然后把进度放到session中 另外写
  • 查看哪个进程占用了8005端口,并杀死占用端口的进程。

    查看哪个进程占用了8005端口 netstat ano findstr 8005 返回进程号 通过进程号杀死占用端口的进程 taskkill PID 19288 F 杀死该进程 F是强制删除
  • C++—返回值优化

    返回值优化 Return value optimization 缩写为RVO 是C 的一项编译优化技术 即删除保持函数返回值的临时对象 这可能会省略两次复制构造函数 当一个函数返回一个对象实例 一个临时对象将被创建并通过复制构造函数把目标对
  • 这些Android面试题,成就你高薪就业。

    前言 这些题目都是面试必答题 看看你还有哪些是没有掌握到的 1 说下你所知道的设计模式与使用场景 建造者模式 观察者模式 代理模式 门面模式 单例模式 生产者消费者模式 2 Java语言的特点与OOP思想 这个通过对比来描述 比如面向对象和
  • Leetcode 95. 不同的二叉搜索树 II

    文章目录 题目 代码 9 21 首刷看解析 题目 Leetcode 95 不同的二叉搜索树 II 代码 9 21 首刷看解析 class Solution public vector
  • vue实现动态路由--后台返回路由表(并解决页面刷新,路由找不到的问题)

    先大致说一下自己的思路 其实后台返回的权限表跟我们前端自己配置的路由格式是差不多的 格式可以跟后台沟通 我们需要做的是根据后台返回的路由 然后进行遍历 生成一个本地的路由表 然后利用Router addRouters 这个方法 把我们新生成
  • Jmeter-Android手机端脚本录制

    温馨提示 电脑和手机在同一网络段上 1 打开Jmeter工具 新建一个HTTP代理服务器 2 然后再新建一个线程组 3 在线程组中添加录制控制器 4 打开模拟器 设置 WiFi 长按 修改网络
  • 辅助模块加速收敛,精度大幅提升 移动端实时的NanoDet-Plus来了

    Nanodet目标检测模型完成自动捡球机器人 从零开始 带你用Nanodet目标检测模型完成自动捡球机器人 古月居 开源地址 https github com Coolog Nanodet Robot PathPlanning 作者提出 N
  • 技术分享|SQL和 NoSQL数据库之间的差异:MySQL(VS)MongoDB

    技术分享 SQL和 NoSQL数据库之间的差异 什么是SQL和NoSQL 一 什么是SQL 二 什么是NoSQL SQL VS NoSQL 针对SQL和NoSQL的区别 将基于不同的方面进行比较 MySQL VS MongoDB 在当今市场
  • forEach()退出循环的方法

    在for循环中退出循环有3种方式 return 终止 break 退出整个循环 continue 退出当次循环 forEach 只能识别上面三种退出循环中的return 其它都识别不了 且return在forEach 中相当于continu
  • ThreadLocal失效

    在JDK中 解决线程冲突问题 有两种解决方案 l 给临界区加锁 l 本地化临界区 第一种解决方案的典型代表是Synchonized 第二种的典型代表是ThreadLocal 而CopyOnWrite是这两种方案的融合 ThreadLocal