AtomicInteger 详细解读

AtomicInteger 详细解读

一、原始数据并发写引发的问题

对于共享变量整数的加减操作,当出现并发的情况时,很容易造成线程不安全。

1、代码示例

public class Demo {static int num = 0;public static void main(String[] args) throws InterruptedException {List<Thread> list = new ArrayList<>();for (int i = 0; i < 2; i++) {Thread t = new Thread() {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {num++;}}};t.start();list.add(t);}for (Thread thread : list) {thread.join();}System.out.println(num);}
}

运行结果如下图,结果并不是预期的20000,出现并发问题:
在这里插入图片描述

2、原因分析

  1. 全局变量num作为共享资源:程序运行时,每个线程都会读取num的当前值,然后对其进行加1操作。当多个线程并发执行时,它们可能几乎同时读到num的同一个值,然后各自加1,最后写回。这样就导致了部分加1操作丢失,因为多个线程实际上是基于相同的初始值执行加法,而不是基于上一个线程已经更新后的值。

  2. 缺乏同步机制:在多线程编程中,为了防止这种数据竞争(data race)问题,通常需要使用锁或其他同步工具来确保同一时间只有一个线程能够访问和修改共享资源。

二、使用AtomicInteger改写

​ 上述示例中,我们可以使用锁机制,来保证线程安全,但锁的粒度太大,会造成一定程度的性能损耗,推荐使用 AtomicInteger。

改写代码如下:

public class Demo {//    static int num = 0;static AtomicInteger num = new AtomicInteger();public static void main(String[] args) throws InterruptedException {List<Thread> list = new ArrayList<>();for (int i = 0; i < 2; i++) {Thread t = new Thread() {@Overridepublic void run() {for (int i = 0; i < 10000; i++) {
//                        num++;num.getAndIncrement();}}};t.start();list.add(t);}for (Thread thread : list) {thread.join();}System.out.println(num);}
}

运行结果:

在这里插入图片描述

三、 AtomicInteger底层原理

源码追踪
在这里插入图片描述

AtomicInteger实现线程安全的自增,从底层原理出发,举例来说:

​ 当线程A和线程B同时尝试对AtomicInteger的值进行自增操作时,如果出现了并发情况,我们可以这样理解其底层原理及如何确保线程安全的:

  1. 初始状态:假设AtomicInteger的当前值为X

  2. 并发尝试

    • 线程A读取到当前值X
    • 几乎同时,线程B也读取到了相同的值X(因为两个线程读操作之间没有互斥,这是并发冲突的根源)。
  3. CAS操作介入

    • 线程A操作:线程A尝试通过CAS操作将值从X更改为X+1。这个操作包括三个步骤:检查当前值是否仍为期望值X,如果是,则更新为X+1;如果不是(即值已经被其他线程改变),则操作失败。
    • 假设线程A的CAS操作成功,此时AtomicInteger的值变为X+1
  4. 线程B的处理

    • 线程B随后尝试执行同样的CAS操作,但因为线程A已经将值更新为X+1,线程B发现当前值不再等于它期望的旧值X,因此线程B的CAS操作失败。
    • CAS操作失败后,线程B通常会重新读取当前值(现在已经是X+1),然后再次尝试CAS操作,这次期望值为X+1,试图将其更新为X+2。这个重试过程确保了线程B最终能够成功执行自增,并且不会丢失更新。

通过这样的机制,AtomicInteger确保了即使在高并发情况下,每个线程的自增操作都能够正确反映到最终结果中,从而实现了线程安全的自增。这个过程是非阻塞的,意味着线程不会因为等待锁而被挂起,提高了效率。

设计思想:

​ 对于悲观锁,认为数据发生并发冲突的概率很大,读操作之前就上锁。synchronized关键字、ReentrantLock都是悲观锁的典型。

​ 对于乐观锁,认为数据发生并发冲突的概率比较小,读操作之前不上锁。等到写操作的时候,再判断数据在此期间是否被其他线程修改了。如果被其他线程修改了,就把数据重新读出来,重复该过程; 如果没有被修改,就写回去。判断数据是否被修改,同时写回新值,这两个操作要合成一个原子操作, 也就是CAS ( Compare And Set )。 AtomicInteger的实现就是典型的乐观锁。

程修改了,就把数据重新读出来,重复该过程; 如果没有被修改,就写回去。判断数据是否被修改,同时写回新值,这两个操作要合成一个原子操作, 也就是CAS ( Compare And Set )。 AtomicInteger的实现就是典型的乐观锁。

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

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

相关文章

CSS 根据子元素选择父元素,并设置父元素的样式

场景举例&#xff1a;当子元素有增加了一个class时&#xff0c;需要影响其父元素的样式 可以使用":has"伪类来实现选择父元素的效果 <style>.parent:has(.child){background-color: #eee;}p{width:100px;border:1px solid #000;} </style> <body>…

webpack优化构建速度示例-externals:

externals 配置项主要用于防止将某些 import 的包&#xff08;package&#xff09;打包到 bundle 中&#xff0c;而是在运行时&#xff08;runtime&#xff09;再从外部获取这些扩展依赖&#xff08;external dependencies&#xff09;。这样做的主要目的是为了解决打包文件过大…

Linux上安装python指南

公司的linux服务器上只有自带的python2,折腾了一下安装python3,后来在网上搜发现装miniconda会更加方便。 1、 下载miniconda安装包 清华镜像下载&#xff1a;https://mirrors.tuna.tsinghua.edu.cn/anaconda/miniconda/ 点这里下载 2、 上传Linux安装 #安装在/usr/local/mini…

非成对意象翻译中的内容制约范式再思考

Rethinking the Paradigm of Content Constraints in Unpaired Image-to-Image Translation 非成对意象翻译中的内容制约范式再思考 Xiuding Cai1 2, Yaoyao Zhu1 2, Dong Miao1 2, Linjie Fu1 2, Yu Yao1 2 蔡秀定 1 2 、朱瑶瑶 1 2 、苗东 1 2 、付林杰 1 2 、余瑶 1 2 Corre…

4-1 综合应用延申:RYU北向接口的应用实现案例

ryu控制器在SDN三层架构中是处于中间层&#xff0c;&#xff08;如图1&#xff09; 图1 SDN三层架构 如何实现与应用层的通信&#xff0c;如图1所示&#xff0c;实现RYU控制器与应用层&#xff08;如开发应用web界面时数据可视化平台&#xff09;数据通信就需要利用SDN的北向…

ssm基于BS的项目监管系统+jsp论文

系统简介 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#xff0c;还是安全性&#xff0c;还是可操作性等各个方面来讲&#xff0c;遇到了互联网时代才发现能补上…

Java | 增强for底层工作机制

✍&#x1f3fc;作者&#xff1a;周棋洛&#xff0c;bilidown开发者。 ♉星座&#xff1a;金牛座 &#x1f3e0;主页&#xff1a;我的个人网站 &#x1f310;关键&#xff1a;Java 增强for 工作机制 目录 引言增强for循环语法增强for工作机制探究简单总结1.对于实现了Iterable接…

【Git教程】(十九)合并小型项目 — 概述及使用要求,执行过程及其实现,替代解决方案 ~

Git教程 合并小型项目 1️⃣ 概述2️⃣ 使用要求3️⃣ 执行过程及其实现 在项目的初始阶段&#xff0c;往往需要针对重要的设计决策和技术实现原型实验。当原型评估结束后&#xff0c;需要将那些成功的原型合并起来称为整个项目的初始版本。 在这样的情景中&#xff0c;各个原…

Hadoop Java API操作 及读取序列化文件(04-05-06)

针对于04-05-06班级整合。 1.创建java项目 2.修改pom.xml文件 添加依赖 <dependencies><dependency><groupId>org.apache.hadoop</groupId><artifactId>hadoop-client</artifactId><version>3.1.4</version></dependenc…

RocketMQ:新增consumer消费组group从最新消息开始消费skip last offset message

场景 想创建一个新的consumer去消费一个已经再使用的topic时&#xff0c;默认情况下会从topic中的第一条消息开始消费&#xff0c;大多数情况是需要从最新的消息开始。然后再使用CONSUME_FROM_LAST_OFFSET设置时并不会对新的consumer生效&#xff0c;它只是在停用consumer重新启…

【MySQL】表的增删改查 | CRUD | 新增 | 查询 | 修改 | 删除 | 数据库约束

文章目录 表的增删改查一、CRUD1.新增&#xff08;Create&#xff09;1.插入多行2.指定列多行插入3.插入datetime类型4.插入当前时间5.插入查询的结果 2.查询&#xff08;Retrieve&#xff09;1.全列查询 *2.指定列查询3.查询字段为表达式4.指定别名 as5.去重 distinct6.排序 o…

算法分析与设计复习__递归方程与分治

总结自&#xff1a;【算法设计与分析】期末考试突击课_哔哩哔哩_bilibili 1.递归&#xff0c;递归方程 1.1递归条件: 1.一个问题的解可以分解为几个子问题的解&#xff1b; 2.这个问题与分解之后的子问题&#xff0c;除了数据规模不同&#xff0c;求解思路完全一样; 3.存在…