首页 星云 工具 资源 星选 资讯 热门工具
:

PDF转图片 完全免费 小红书视频下载 无水印 抖音视频下载 无水印 数字星空

Volatile不保证原子性及解决方案

编程知识
2024年07月19日 15:15

原子性的意义

原子性特别是在并发编程领域,是一个极其重要的概念,原子性指的是一个操作或一组操作要么全部执行成功,要么全部不执行,不会出现部分执行的情况。这意味着原子性操作是不可分割的,它们在执行过程中不会被其他操作中断或干扰。

原子性的意义在于它保证了数据的一致性和程序的正确性。在多线程或多进程的环境中,当多个操作同时访问和修改共享数据时,如果没有原子性保证,可能会导致数据不一致或不确定的结果。例如,如果一个线程在读取某个数据时,另一个线程同时修改了这个数据,那么第一个线程读取到的数据可能是不正确的。通过确保操作的原子性,可以避免这种情况,从而维护数据的完整性和程序的正确执行。

了解了上面的原子性的重要概念后,接下来一起聊一聊 volatile 关键字。

volatile 关键字在 Java 中用于确保变量的更新对所有线程都是可见的,但它并不保证复合操作的原子性。这意味着当多个线程同时访问一个 volatile 变量时,可能会遇到读取不一致的问题,尽管它们不会看到部分更新的值。

Volatile 的限制

  • 不保证原子性:volatile 变量的单个读写操作是原子的,但复合操作(如自增或同步块)不是原子的。
  • 不保证顺序性:volatile 变量的读写操作不会与其他操作(如非 volatile 变量的读写)发生重排序。

一个例子

用一个示例来解释会更清楚点,假如我们有一段代码是这样的:

class Counter {
    private volatile int count = 0;

    void increment() {
        count++;
    }

    int getCount() {
        return count;
    }
}

尽管 count 是 volatile 变量,但 increment 方法中的复合操作 count++(读取-增加-写入)不是原子的。因此,在多线程环境中,多个线程可能会同时读取相同的初始值,然后增加它,导致最终值低于预期。

volatile 不保证原子性的代码验证

以下是一个简单的 Java 程序,演示了 volatile 变量在多线程环境中不保证复合操作原子性的问题:


public class VolatileTest {
    private static volatile int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        int numberOfThreads = 10000;
        Thread[] threads = new Thread[numberOfThreads];

        for (int i = 0; i < numberOfThreads; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    counter++;
                }
            });
            threads[i].start();
        }

        for (int i = 0; i < numberOfThreads; i++) {
            threads[i].join();
        }

        System.out.println("Expected count: " + (numberOfThreads * 100));
        System.out.println("Actual count: " + counter);
    }
}

在这个例子中:

  • counter 是一个 volatile 变量。
  • 每个线程都会对 counter 执行 100 次自增操作。
  • 理论上,如果 counter++ 是原子的,最终的 counter 值应该是 10000 * 100。

然而,由于 counter++ 包含三个操作:读取 counter 的值、增加 1、写回 counter 的值,这些操作不是原子的。因此,在多线程环境中,最终的 counter 值通常会小于预期值,这证明了 volatile 变量不保证复合操作的原子性。

解决方案

1. 使用 synchronized 方法或块:

  • 将访问 volatile 变量的方法或代码块声明为 synchronized,确保原子性和可见性。
class Counter {
    private volatile int count = 0;

    synchronized void increment() {
        count++;
    }

    synchronized int getCount() {
        return count;
    }
}

2. 使用 AtomicInteger 类:

java.util.concurrent.atomic 包中的 AtomicInteger 提供了原子操作,可以替代 volatile 变量。


import java.util.concurrent.atomic.AtomicInteger;

class Counter {
    private AtomicInteger count = new AtomicInteger(0);

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

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

3. 使用锁(如 ReentrantLock):

使用显式锁(如 ReentrantLock)来同步访问 volatile 变量的代码块。


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Counter {
    private volatile int count = 0;
    private final Lock lock = new ReentrantLock();

    void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

使用volatile变量的正确使用场景

如果操作是简单的读写,并且你只需要保证可见性,可以使用 volatile。但对于复合操作,可以使用上述其他方法来实现,通过这些方法,可以确保在多线程环境中对共享资源的正确同步和可见性。

From:https://www.cnblogs.com/wgjava/p/18311697
本文地址: http://shuzixingkong.net/article/199
0评论
提交 加载更多评论
其他文章 bitwarden本地搭建(无需购买SSL证书)
bitwarden本地搭建(无需购买SSL证书) 在安装之前,笔者在这里先声明一下,我安装bitwarden使用的操作环境为ArchLinux,我的想法是,因为这只是一个“密码本”,并且最好能保证其能够在开机后占用尽量少的内存让密码本保持稳定运行。在此前提下,我选择了干净整洁的ArchLinux,关
bitwarden本地搭建(无需购买SSL证书) bitwarden本地搭建(无需购买SSL证书) bitwarden本地搭建(无需购买SSL证书)
开源!开源一个flutter实现的古诗拼图游戏
去年(2023年)年底我初学flutter,看了一些文档和教程,想找个东西来练练手。 小时候看过一个关于历史名人儿时事迹的短片,有一集是讲周总理的,有一个细节我记得很清楚:幼年周恩来经常要做一个游戏--有一堆纸片,每片纸上一个字,他要一个一个字拼起来拼成一首诗。 很多年前我就想,或许可以把这个游戏做
开源!开源一个flutter实现的古诗拼图游戏 开源!开源一个flutter实现的古诗拼图游戏 开源!开源一个flutter实现的古诗拼图游戏
诞生记(一)——上线一个小程序最低要花多少钱?
我是一个很懒的人,很少写博客。为什么?因为技术发展太快了,刚学习记录下来过段时间来看看,发现全都过时了。太浪费感情了。 曾经我也是一个软粉,一个.Net开发者,同学都入坑Android、Java踩着时代的红利拿高薪的时候。我却始终爱着微软。一直到微软彻底抛弃Windows Phone10的时候我才死
诞生记(一)——上线一个小程序最低要花多少钱? 诞生记(一)——上线一个小程序最低要花多少钱?
【VMware VCF】VMware Cloud Foundation Part 02:部署 Cloud Builder。
VMware Cloud Builder 是用于构建 VMware Cloud Foundation 第一个管理域的自动化部署工具,通过将一个预定义信息的 Excel 参数表导入到 Cloud Builder 以启动 VCF 的初始构建过程(Bring-up)。VMware Cloud Builde
【VMware VCF】VMware Cloud Foundation Part 02:部署 Cloud Builder。 【VMware VCF】VMware Cloud Foundation Part 02:部署 Cloud Builder。 【VMware VCF】VMware Cloud Foundation Part 02:部署 Cloud Builder。
前端太卷了,不玩了,写写node.js全栈涨工资,赶紧学起来吧!!!!!
首先聊下node.js的优缺点和应用场景 Node.js的优点和应用场景 Node.js作为后端开发的选择具有许多优点,以下是其中一些: 高性能: Node.js采用了事件驱动、非阻塞I/O模型,使得它能够处理大量并发请求而不会阻塞线程,从而具有出色的性能表现。 轻量级和高效: Node.js的设计
KU FPGA FLASH boot失败debug
原因 新板子回来后,测试flash 烧录正常,但是无法BOOT,此时SPI设置为X4模式,使用内部时钟,速度90M。烧录过程不报错,校验也正常。 FLASH理论支持最大速度108M,90M应该还好。另外板卡预留了EMCCLK外部时钟模式,速率100M 也不可行。 此时约束如下: set_proper
KU FPGA FLASH boot失败debug KU FPGA FLASH boot失败debug KU FPGA FLASH boot失败debug
ComfyUI进阶:Comfyroll插件 (四)
ComfyUI进阶:Comfyroll插件 (四)前言:学习ComfyUI是一场持久战,而Comfyroll 是一款功能强大的自定义节点集合,专为 ComfyUI 用户打造,旨在提供更加丰富和专业的图像生成与编辑工具。借助这些节点,用户可以在静态图像的精细调整和动态动画的复杂构建方面进行深入探索。C
ComfyUI进阶:Comfyroll插件 (四) ComfyUI进阶:Comfyroll插件 (四) ComfyUI进阶:Comfyroll插件 (四)
「比赛记录」CF Round 954 (Div. 3)
Codeforces Round 954 (Div. 3) 题目列表: A. X Axis B. Matrix Stabilization C. Update Queries D. Mathematical Problem E. Beautiful Array F. Non-academic Pro