详解Java中的原子操作

news2024/9/24 15:21:01

第1章:什么是原子操作

大家好,我是小黑,面试中一个经常被提起的话题就是“原子操作”。那么,到底什么是原子操作呢?在编程里,当咱们谈论“原子操作”时,其实是指那些在执行过程中不会被线程调度机制打断的操作。这种操作要么完全执行,要么完全不执行,没有中间状态。这就像是化学里的原子,不可分割,要么存在,要么不存在。

举个简单的例子,想象一下,小黑在网上银行转账给朋友。这个操作要么完整完成——钱从小黑的账户转到朋友账户,要么根本就不发生——钱还在小黑的账户里。如果转账过程中出现问题,系统不会说“转了一半的钱”,要么就是全转了,要么就是一分没转。这就是原子操作的一个生活实例。

在Java中,原子操作尤为重要,尤其是在多线程环境中。想象一下,如果小黑在操作一个共享变量时,这个操作被其他线程打断,那会发生什么?可能会导致数据不一致,或者更糟糕的情况。因此,保证操作的原子性在并发编程中是非常重要的。

第2章:Java中原子操作的基础

要理解Java中的原子操作,首先得了解一下Java内存模型(JMM)。简单来说,JMM是一种抽象的概念,它描述了Java在内存中如何存储共享变量以及这些变量如何在多线程间交互。JMM定义了线程和主内存之间的关系,以及如何通过同步来保证共享变量的可见性和顺序性。

在多线程环境中,每个线程都有自己的工作内存,用于存储使用中的共享变量的副本。当线程对这些变量进行读写操作时,它们实际上是在操作这些副本。只有通过同步操作,这些变量的值才会真正地在主内存和工作内存间传递。

那么,原子操作和JMM有什么关系呢?原子操作保证了在单个操作中,变量的读取、修改和写回都是不可分割的。这就确保了即使在多线程环境中,这些操作也能保持一致性和正确性。

让咱们来看一个简单的Java代码示例,展示非原子操作的问题:

public class Counter {
    private int count = 0;

    public void increment() {
        count++; // 非原子操作
    }

    public int getCount() {
        return count;
    }
}

在这个例子中,count++看似是一个简单的操作,但实际上它包含了三个步骤:读取count的值,增加1,然后写回新的值。在多线程环境中,如果两个线程同时执行increment()方法,它们可能读到同一个count值,结果就是count只增加了1,而不是2。这就是非原子操作可能带来的问题。

第3章:Java原子类概览

原子类的基本概念

原子类,顾名思义,就是用来进行原子操作的类。在Java中,这些类提供了一种线程安全的方式来操作单个变量,无需使用synchronized关键字。原子类的操作是基于CAS(Compare-And-Swap,比较并交换)机制实现的,这是一种轻量级的同步策略,比传统的锁机制更高效。

常见的原子类

让咱们来看几个常用的原子类:

  1. AtomicInteger:提供了一个可以原子性更新的int值。
  2. AtomicLong:和AtomicInteger类似,但它是针对long类型的。
  3. AtomicBoolean:提供了一个可以原子性更新的boolean值。
  4. AtomicReference:提供了一个可以原子性更新的对象引用。
AtomicInteger的使用示例

来看个例子,如何使用AtomicInteger

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet(); // 原子性地增加count的值
    }

    public int getCount() {
        return count.get();
    }
}

class Main {
    public static void main(String[] args) {
        AtomicCounter counter = new AtomicCounter();
        // 模拟多线程环境
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                counter.increment();
            }).start();
        }

        // 等待所有线程完成
        try {
            Thread.sleep(2000); // 等待足够长的时间以确保所有线程都执行完毕
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("最终计数: " + counter.getCount()); // 正确输出1000
    }
}

在这个示例中,AtomicCounter类使用了AtomicInteger来保证count的增加操作是原子的。这就解决了之前提到的非原子操作可能导致的问题。无论多少线程同时调用increment()方法,每次调用都会安全地将count增加1。

第4章:深入探讨原子类的实现原理

CAS机制简介

CAS机制包含三个主要的操作数:内存位置(V)、预期原值(A)和新值(B)。操作的逻辑是:“我认为V应该是A,如果是,就把V更新成B,否则,不做任何操作。”这个过程是原子的,意味着在这个操作执行期间,没有其他线程可以改变这个内存位置的值。

CAS的优点和挑战

CAS的主要优点是它提供了一种无锁的方式来实现并发控制。相比于传统的锁机制,CAS通常能提供更好的性能,并减少了死锁的风险。

但CAS也有它的挑战。其中一个主要问题是“ABA问题”。如果一个变量原来是A,变成了B,然后又变回A,使用CAS的线程可能会错误地认为这个变量没有被其他线程修改过。为了解决这个问题,Java提供了AtomicStampedReference类,它通过维护一个“时间戳”来记录变量的修改次数。

CAS在AtomicInteger中的应用

来看一个具体的例子,展示AtomicInteger是如何利用CAS机制的:

public class CASExample {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        int currentValue;
        int newValue;
        do {
            currentValue = count.get(); // 获取当前值
            newValue = currentValue + 1; // 计算新值
        } while (!count.compareAndSet(currentValue, newValue)); // CAS操作
        // 如果当前值不等于预期值,则循环尝试,直到成功
    }

    public int getCount() {
        return count.get();
    }
}

class Main {
    public static void main(String[] args) {
        CASExample example = new CASExample();
        // 模拟多线程环境
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                example.increment();
            }).start();
        }

        // 等待所有线程完成
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("最终计数: " + example.getCount());
    }
}

在这个例子中,increment()方法用一个do-while循环来保证增加操作的原子性。如果在执行CAS操作时,当前值不是预期值,意味着其他线程已经修改了这个值,那么循环会继续,直到成功更新。

通过这种方式,AtomicInteger确保了即使在高并发的环境下,增加操作也是原子的,保证了数据的一致性和线程安全。

第5章:原子操作与并发编程

原子操作在并发编程中的应用

在并发编程中,原子操作确保了当多个线程尝试同时更新同一个变量时,这个变量的值不会丢失或者损坏。这对于维护数据的一致性和程序的稳定性至关重要。

案例分析

让我们通过一个具体的案例来看看原子操作是如何工作的。假设咱们需要编写一个程序,来统计一个网站的访问量。在高并访问量的情况下,可能有成百上千的用户同时访问这个网站,这时就需要一个能够安全地处理多线程并发访问的计数器。

使用非原子操作的计数器可能会导致一些访问量丢失,因为当多个线程同时读取和更新计数器的值时,一些更新可能会被覆盖。而使用原子操作的计数器可以确保每次访问都被准确地记录。

import java.util.concurrent.atomic.AtomicInteger;

public class WebsiteVisitsCounter {
    private AtomicInteger visitsCount = new AtomicInteger();

    public void visit() {
        visitsCount.incrementAndGet(); // 原子操作
    }

    public int getTotalVisits() {
        return visitsCount.get();
    }
}

class Main {
    public static void main(String[] args) {
        WebsiteVisitsCounter counter = new WebsiteVisitsCounter();

        // 模拟1000个用户同时访问网站
        for (int i = 0; i < 1000; i++) {
            new Thread(counter::visit).start();
        }

        // 等待线程结束
        try {
            Thread.sleep(2000); // 假设足够的时间让所有线程执行完毕
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("总访问量: " + counter.getTotalVisits()); // 应该输出1000
    }
}

在这个例子中,WebsiteVisitsCounter使用了AtomicInteger来确保访问次数的计数是准确的,即使在高并发的情况下。每次调用visit方法都会安全地增加visitsCount,无论多少线程同时访问它。

第6章:原子类的性能考量

原子类与传统同步机制的性能比较

传统的同步机制,比如使用synchronized关键字,通过锁来控制对共享资源的访问。这种方法简单直观,但在高并发环境下可能导致性能瓶颈。原因是当一个线程持有锁时,其他所有需要这个锁的线程都必须等待,这就可能导致大量线程处于等待状态,从而降低了应用程序的整体性能。

相比之下,原子类利用CAS(Compare-And-Swap)机制来实现同步。这种机制不需要阻塞线程,因此在处理高并发数据时通常能提供更好的性能。但CAS也不是完美无缺的,特别是在高冲突环境下,频繁的CAS操作可能会导致性能下降,这种情况被称为“自旋”。

性能分析实例

让我们通过一个简单的实验来比较使用synchronized和原子类的性能差异。假设有一个简单的计数器,咱们分别用synchronized方法和AtomicInteger来实现,然后比较它们在多线程环境下的性能。

public class SynchronizedCounter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class AtomicCounter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}

在这个例子中,SynchronizedCounter使用synchronized关键字来保证计数器的线程安全,而AtomicCounter则使用AtomicInteger。如果咱们在一个高并发的环境下测试这两个计数器,通常会发现AtomicCounter的性能要优于SynchronizedCounter。原因在于synchronized会引起线程的阻塞和唤醒,而AtomicInteger利用CAS实现非阻塞的同步。

第7章:原子类的高级用法

AtomicReference的使用

AtomicReference类提供了一种方式来原子性地更新对象引用。这意味着咱们可以安全地在多线程环境中更改对象引用,而无需担心线程安全问题。

来看一个AtomicReference的例子:

import java.util.concurrent.atomic.AtomicReference;

public class AtomicReferenceExample {
    private AtomicReference<String> currentSetting = new AtomicReference<>("默认设置");

    public void updateSetting(String newSetting) {
        currentSetting.set(newSetting);
    }

    public String getSetting() {
        return currentSetting.get();
    }
}

class Main {
    public static void main(String[] args) {
        AtomicReferenceExample example = new AtomicReferenceExample();

        // 模拟多线程环境更新设置
        new Thread(() -> example.updateSetting("自定义设置1")).start();
        new Thread(() -> example.updateSetting("自定义设置2")).start();

        System.out.println("当前设置: " + example.getSetting());
    }
}

在这个例子中,AtomicReferenceExample类使用AtomicReference来保持currentSetting的线程安全。无论多少线程尝试更新这个设置,AtomicReference都能确保操作的原子性。

AtomicStampedReference的使用

AtomicStampedReference类是对AtomicReference的一个扩展,它解决了所谓的“ABA问题”。除了对象引用之外,AtomicStampedReference还维护了一个“时间戳”,这个时间戳在每次对象更新时都会改变。

来看一个AtomicStampedReference的例子:

import java.util.concurrent.atomic.AtomicStampedReference;

public class AtomicStampedReferenceExample {
    private AtomicStampedReference<String> currentSetting =
            new AtomicStampedReference<>("默认设置", 0);

    public void updateSetting(String newSetting) {
        int stamp = currentSetting.getStamp();
        currentSetting.compareAndSet(currentSetting.getReference(), newSetting, stamp, stamp + 1);
    }

    public String getSetting() {
        return currentSetting.getReference();
    }
}

class Main {
    public static void main(String[] args) {
        AtomicStampedReferenceExample example = new AtomicStampedReferenceExample();

        // 模拟多线程环境更新设置
        new Thread(() -> example.updateSetting("自定义设置1")).start();
        new Thread(() -> example.updateSetting("自定义设置2")).start();

        System.out.println("当前设置: " + example.getSetting());
    }
}

在这个例子中,每次更新currentSetting时,时间戳stamp都会增加。这就意味着即使一个值从A变成B再变回A,时间戳也会反映出这个变化,从而避免了ABA问题。

第8章:总结

原子操作的重要性

在多线程编程中,原子操作是确保数据一致性和线程安全的关键。通过原子类,如AtomicIntegerAtomicBooleanAtomicReference等,Java提供了一种有效的方式来进行线程安全的操作,无需担心数据损坏或者线程冲突的问题。这对于构建高效且健壮的并发应用程序至关重要。

原子类的高效性

我们还讨论了原子类相比传统锁机制(如synchronized)在性能上的优势。特别是在高并发环境下,原子类能够提供更好的性能,减少资源的等待时间,从而提高程序的整体效率。

面临的挑战和应对策略

尽管原子操作提供了许多优势,但它们也面临着一些挑战,比如在高冲突环境下的性能问题。为了应对这些挑战,Java持续在发展中,引入了更多高级原子类,如AtomicStampedReference,来解决这些问题。

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

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

相关文章

thinkphp学习03-url访问模式

多应用&#xff1a;http://serverName/index.php/应用/控制器/操作/参数/值…&#xff1b;单应用&#xff1a;http://serverName/index.php/控制器/操作/参数/值…&#xff1b; http://localhost:8506/index.php Contoller下新建Test.php <?php namespace app\controlle…

电脑丢失dll文件怎么办,dll修复工具可一键修复dll问题

在计算机使用过程中&#xff0c;我们经常会遇到一些错误提示&#xff0c;其中最常见的就是“找不到指定的模块”或“无法找到某某.dll文件”。这种情况通常是由于dll文件丢失或损坏导致的。那么&#xff0c;究竟是什么原因导致了dll文件的丢失呢&#xff1f;又该如何预防dll文件…

labelme的安装

首先尝试在(openmmlab)的python3.8的环境下安装&#xff08;失败&#xff09;。应该是我环境其他部分不对&#xff0c;和python版本应该没什么关系。&#xff08;后续&#xff0c;创建新的环境后成功&#xff0c;可直接看最后一部分。&#xff09; 首先安装是没问题的 pip in…

快手面经总结(2024最新)

快手 面经1-一面 开始先是手撕算法两道 自我介绍两道手撕 将字符串转化为整数 (这里当时出现溢出值问题&#xff0c;进行了思考解决&#xff0c;写了两种方式)synchronize &#xff0c; 可以使用的几种形式&#xff0c;代码写出 操作系统 和 数据结构 hash解决冲突 &#xff…

e2studio开发STHS34PF80人体存在传感器(1)----获取人体存在状态

e2studio开发STHS34PF80人体存在传感器.1--获取人体存在状态 概述视频教学样品申请完整代码下载主要特点硬件准备接口最小系统图新建工程工程模板保存工程路径芯片配置工程模板选择时钟设置UART配置UART属性配置设置e2studio堆栈e2studio的重定向printf设置R_SCI_UART_Open()函…

canal本地搭建以及运行

具体的文档可参考官网文档&#xff1a;https://github.com/alibaba/canal/wiki canal [kənl]&#xff0c;译意为水道/管道/沟渠&#xff0c;主要用途是基于 MySQL 数据库增量日志解析&#xff0c;提供增量数据订阅和消费 工作原理 canal 模拟 MySQL slave 的交互协议&#x…

解决Canvas画图清晰度问题

最近在开发Web端远程桌面的时候遇到的一个问题&#xff0c;解决记录一下&#xff0c;分享给各位有需要用到的朋友。 先吹下水&#xff1a;远程桌面的连接我们是通过Websocket连接后&#xff0c;后端不断返回远程端的界面二进制数据流&#xff0c;我接收到之后转为图像&#xf…

mxxWechatBot微信机器人自定义接口支持配置多个关键词、多个群聊和好友

大家伙&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。 前言 注意&#xff1a; 免责声明&#xff1a;该工具仅供学习使用&#xff0c;禁止使用该工具从事违法活动&#xff0c;否则永久拉黑​封禁账号。本工具我不会绝对保证对你的账号没有影响…

案例精选|淄博绿能燃气工程有限公司日志审计系统建设方案

淄博绿能燃气工程有限公司&#xff0c;成立于1994年&#xff0c;前身为淄博市煤气公司管道液化气分公司。公司业务主要涉及天然气、液化气等市政工程施工及城镇燃气供应等领域&#xff0c;具有市政公用工程施工总承包二级资质&#xff0c;《压力管道安装许可证》压力管道安装GB…

看图识熊(三)

使用Windows Machine Learning加载ONNX模型并推理 环境要求 Windows Machine Learning支持在Windows应用程序中加载并使用训练好的机器学习模型。Windows 10从10.0.17763.0版本开始提供这套推理引擎&#xff0c;所以需要安装17763版本的Windows 10 SDK进行开发&#xff0c;并…

实战Flink Java api消费kafka实时数据落盘HDFS

文章目录 1 需求分析2 实验过程2.1 启动服务程序2.2 启动kafka生产 3 Java API 开发3.1 依赖3.2 代码部分 4 实验验证STEP1STEP2STEP3 5 时间窗口 1 需求分析 在Java api中&#xff0c;使用flink本地模式&#xff0c;消费kafka主题&#xff0c;并直接将数据存入hdfs中。 flin…

秒懂百科,C++如此简单丨第十五天:指针

目录 必看信息 Everyday English 前言 &#x1f4dd;了解指针 &#x1f4dd;定义指针 &#x1f4dd;分析指针 &#x1f4dd;运用指针 总结 必看信息 ▶本篇文章由爱编程的小芒果原创&#xff0c;未经许可&#xff0c;严禁转载。 ▶本篇文章被收录于秒懂百科&#xff0c…

网络层协议及IP编址

0x00 前言 本节为网络层协议及IP编址内容 IP地址的范围&#xff1a;0.0.0.0-255.255.255.255 IP分为网络位以及主机位。子网划分就是向主机位借位。 网络层协议 IPICMP&#xff08;internet Control message protocol&#xff09;IPX IP协议的作用 为网络层的设备提供逻…

2023湾区产城创新大会:培育数字化供应链金融新时代

2023年12月26日&#xff0c;由南方报业传媒集团指导&#xff0c;南方报业传媒集团深圳分社主办的“新质新力——2023湾区产城创新大会”在深圳举行。大会聚集里国内产城研究领域的专家学者以及来自产业园区、金融机构、企业的代表&#xff0c;以新兴产业发展为议题&#xff0c;…

【计算机网络】网络基础--协议/网络协议/网络传输流程/地址管理

文章目录 一、计算机网络背景二、协议1.协议是什么2.为什么要有协议 三、网络协议1.为什么要进行协议分层2.OSI七层模型3.TCP/IP五层(或四层)模型 四、网络传输基本流程1.协议报头2.局域网3.数据包封装和分用4.网络传输流程图 五、网络中的地址管理1.认识IP地址2.认识MAC地址3.…

C++与数据库MySQL锁——模拟订票(事务)

假设订票的时候&#xff0c;好几个人同时进入&#xff0c;查看这张票是否售出&#xff0c;假如同时购买了这张票&#xff0c;那对于售票行业来说&#xff0c;可能就会发生低级错误。那么如何避免这类事情发生呢&#xff1f; 解决办法&#xff1a; 在一个人访问的时候&#xf…

DataFunSummit:2023年知识图谱在线峰会-核心PPT资料下载

一、峰会简介 AIGC&#xff0c;ChatGPT以及发布的GPT-4相信已经给大家带来足够的冲击&#xff0c;那么对于知识图谱的应用产生哪些变化和变革&#xff1f;知识图谱在其中如何发挥作用呢&#xff1f;通过LLM是否有可能辅助创建通用大规模知识图谱&#xff1f;AIGC时代下行业知识…

burpsuite专业版的安装和破解(2024年最新)

burpsuite专业版的安装和破解&#xff08;2024年最新&#xff09; 简介视频教程下载BP专业版第一步第二步&#xff1a;下载第三步第四步&#xff1a;打开powershell界面第五步&#xff1a;在powershell中执行BurpLoaderKeygen.jar文件第六步&#xff1a;破解第七步&#xff1a;…

聚道云软件连接器,助力某钢铁行业公司实现发票信息自动同步

客户介绍&#xff1a; 某钢铁行业公司是一家大型现代化民营钢铁企业&#xff0c;拥有覆盖钢铁全产业链的冶金装备、技术和全过程信息系统。公司业务涉及钢铁、煤炭、房产等行业&#xff0c;多年来一直保持着稳健的发展态势。 添加图片注释&#xff0c;不超过 140 字&#xff0…

VlnPlot画的其实不是原始数据

昨天的推文描述了让小提琴图肚子变大的做法&#xff1a;让你的小提琴肚子大起来‍‍‍‍‍‍‍‍‍‍‍‍‍ 在此说明&#xff1a;这种不考虑后果&#xff0c;就让肚子大起来的做法是不严谨的。如需使用&#xff0c;建议将原始图和修改图放在一起对比&#xff0c;且在文章中注…