详解Java中的原子操作

第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.mfbz.cn/a/300228.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python | 基于Mediapipe框架的手势识别系统

一、项目要求 1、题目 本题着力于解决会商演示系统中的非接触式人机交互问题&#xff0c;具体而言&#xff0c;其核心问题就是通过计算机视觉技术实现对基于视频流的手势动作进行实时检测和识别。通过摄像头采集并识别控制者连续的手势动作&#xff0c;完成包括点击、平移、缩放…

关于无人机上层控制的PID算法的思考

一、前言 背景介绍&#xff1a;PID虽然出现了很多年&#xff0c;但是目前工业界还是把PID作为主流的控制算法&#xff08;尽管学术界有很多非常时尚的控制算法&#xff0c;包括鲁邦控制&#xff0c;神经网络控制等等&#xff09;&#xff0c;PID的算法在于其不需要对系统进行复…

编程语言的生命力

一、目前主流的编程语言 目前流行的编程语言有很多种&#xff0c;可谓是百花齐放、百家争鸣。根据不同的应用场景和领域&#xff0c;有不同的编程语言被广泛使用。一些目前主流的编程语言HTML5、Python、JavaScript 、Java 、C 、PHP 、Swift 等等。 还有许多其他的编程语言&am…

leetcode算法题之递归--综合练习(二)

本章目录 1.N皇后2.有效的数独3.解数独4.单词搜索5.黄金矿工6.不同路径III 1.N皇后 N皇后 class Solution {vector<vector<string>> ret;vector<string> path;int n;bool checkCol[10],checkDig1[20],checkDig2[20]; public:vector<vector<string&g…

1018:奇数偶数和1028:I love 闰年!和1029:三角形判定

1018&#xff1a;奇数偶数 要求&#xff1a;输入一个整数&#xff0c;判断该数是奇数还是偶数。如果该数是奇数就输出“odd”&#xff0c;偶数就输出“even”&#xff08;输出不含双引号&#xff09;。 输入样例&#xff1a;8 输出样例&#xff1a;even 程序流程图&#xff1a…

解决Canvas画图清晰度问题

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

MySQL练习-DDL语法练习

文章目录 1、数据库操作2、表操作3、DDL数据类型 突然想起来好久没写过SQL了&#xff0c;写一下SQL练习一下&#x1f60a; 个人写sql比较喜欢用小写&#x1f601; 什么是DDL&#xff1a;DDL是对数据库和表的操作 在这里练习DLL的时候先不添加约束&#xff0c;后面会把约束集中…

YOLOv8模型yaml结构图理解(逐层分析)

前言 YOLO-V8&#xff08;官网地址&#xff09;&#xff1a;https://github.com/ultralytics/ultralytics 一、yolov8配置yaml文件 YOLOv8的配置文件定义了模型的关键参数和结构&#xff0c;包括类别数、模型尺寸、骨架&#xff08;backbone&#xff09;和头部&#xff08;hea…

迟到的总结:回望 2023 年,期盼 2024 新机会、新挑战

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、RocketMQ&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏…

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

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

Proteus 各版本安装指南

Proteus下载链接 https://pan.baidu.com/s/1vHgg8jK9KSHdxSU9SDy4vQ?pwd0531 1.鼠标右击【Proteus8.15(64bit&#xff09;】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到Proteus8.15(64bit&#xff09; 】。 2.打开解压后的文件夹&#…

FastDFS安装与测试

目录 目标 版本 环境 官方文档 相关概念 安装FastDFS 启动FastDFS 关闭FastDFS 重启FastDFS 用命令测试上传文件 用命令测试下载文件 用命令测试删除文件 用HTTP的方式访问FastDFS中的文件 用HTTP的方式访问FastDFS中的文件整体流程 目标 在Linux服务器上搭建单…

遇见未来的你——感谢你带给我的感悟

目录 一、背景介绍二、思路&方案三、过程1.都说有的人出生就在罗马而有的人却用一辈子都在去向罗马的路上1.1.物质&#xff1a;1.2.精神&#xff1a; 2.做事情要看大再看细3.心存善念&#xff0c;常怀感恩&#xff0c;从小事做起4.所谓的面子在母爱面前像是一粒微尘5.讲道理…

React 中条件渲染的 N 种方法

本文作者系360奇舞团前端开发工程师 条件渲染在React开发中非常重要的功能&#xff0c;它允许开发人员根据条件控制渲染的内容&#xff0c;在创建动态和交互式用户界面方面发挥着至关重要的作用&#xff0c;本文总结了常用的的条件渲染方法。 1.If-else if-else是一种控制流程的…

qt自定义控件的封装

刚学了一个很有意思的东西,前面学了list,Tree,Table三大控件和一部分常用基础控件,但感觉没啥意思,就是用别人的直接用,刚学了一个自定义控件的封装,流程如下: 想把两个不相关的组件封装在一块,直接用ui不行,所以先新添加了qt设计师页面,新添加了一个SmallWidget *ui 在smal…

SLURM作业管理系统之3种作业提交方式

文章目录 前言定义基本概念三种作业提交模式1. 批处理作业&#xff08;采用 sbatch 命令提交&#xff09;2. 交互式作业提交&#xff08;采用 srun 命令提交&#xff09;3. 分配模式作业&#xff08;采用 salloc 命令提交&#xff09; 管理节点部署Slurm常用命令 前言 在高性能…

unity 游戏开发中傻傻分不清URP、HDRP和SRP

文章目录 **URP (Universal Render Pipeline)**:**HDRP (High Definition Render Pipeline)**:**区别**&#xff1a; Unity的URP&#xff08;Universal Render Pipeline&#xff09;和HDRP&#xff08;High Definition Render Pipeline&#xff09;都是基于SRP&#xff08;Scri…

k8s yaml文件pod的生命周期

Pod是k8s中最小限额资源管理组件&#xff0c;也是最小化运行容器化的应用的资源管理对象。 Pod是一个抽象的概念&#xff0c;可以理解为一个或者多个容器化应用的集合。 在一个pod当中运行一个容器是最常用的方式。 在一个pod当中同时运行多个容器&#xff0c;在一个pod当中…

2024阿里云服务器可用区选择方法

阿里云服务器地域和可用区怎么选择&#xff1f;地域是指云服务器所在物理数据中心的位置&#xff0c;地域选择就近选择&#xff0c;访客距离地域所在城市越近网络延迟越低&#xff0c;速度就越快&#xff1b;可用区是指同一个地域下&#xff0c;网络和电力相互独立的区域&#…

Strict MIME type checking is enforced for module scripts per HTML spec.

目录 前言错误信息如下:前言 最近使用docker打包Nginx和vue 为镜像文件,启动镜像时报错 错误信息如下: index89886.js:1 Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Stri…