Volatile关键字与Java原子性的迷宫之旅

在Java并发编程的领域中,有一种看似简单却蕴藏深意的关键字------volatile。它如同一把钥匙,打开了数据一致性与线程间通信的大门。但volatile并非全能,尤其是在原子性面前,它有着自己的局限性。本文将带你穿越迷宫,探索volatile的真谛及其与原子性的关系。

Volatile的面纱:可见性与有序性

Volatile 关键字的核心作用在于确保变量的可见性有序性 。所谓可见性,指的是当一个线程修改了某个volatile变量后,其他线程能够立即看到这一变化。有序性则保证了指令不会被处理器或编译器重排序,从而避免了某些类型的竞争条件。

示例代码:

java 复制代码
public class VolatileVisibility {
    public static volatile boolean ready = false;
    public static int number = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread writerThread = new Thread(() -> {
            number = 42;
            ready = true;
        });

        Thread readerThread = new Thread(() -> {
            while (!ready) {
                Thread.yield();
            }
            System.out.println(number); // 应该输出42
        });

        writerThread.start();
        readerThread.start();

        writerThread.join();
        readerThread.join();
    }
}

在这个例子中,如果没有volatile修饰符,number的更新可能不会被readerThread及时发现,导致输出错误的结果。但加上volatile后,readerThread将能够正确读取到writerThread写入的新值。

原子性的迷雾:Volatile的盲区

虽然volatile能提供可见性和有序性,但它并不能保证原子性。原子性意味着一个操作要么完全执行,要么完全不执行,不会被中断。例如,考虑下面的代码片段:

示例代码:

java 复制代码
public class VolatileNonAtomicity {
    public static volatile int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    count++; // 这个操作不是原子的
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) {
            t.join();
        }

        System.out.println("Expected: 10000, Actual: " + count);
    }
}

尽管countvolatile的,但count++操作并非原子的,因此多个线程同时进行加法操作时,可能会得到比预期少的结果。这是因为count++实际上是由读取、计算、写回三个步骤组成的,而在多线程环境下,这些步骤可能被不同的线程交错执行。

原子性的解决方案:Java并发类库

为了弥补volatile在原子性方面的不足,Java提供了java.util.concurrent.atomic 包,其中包含了一系列原子类,如AtomicIntegerAtomicLong等,它们利用内部的锁或其他同步机制来保证原子性。

示例代码:

java 复制代码
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicSafety {
    public static AtomicInteger atomicCount = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Thread[] threads = new Thread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    atomicCount.incrementAndGet(); // 原子性操作
                }
            });
            threads[i].start();
        }

        for (Thread t : threads) {
            t.join();
        }

        System.out.println("Expected: 10000, Actual: " + atomicCount.get());
    }
}

在这个版本中,atomicCount使用AtomicInteger,其incrementAndGet()方法是原子性的,从而保证了所有线程操作的完整性,输出结果将始终等于预期。

相关推荐
從南走到北6 分钟前
JAVA国际版任务悬赏发布接单系统源码支持IOS+Android+H5
android·java·ios·微信·微信小程序·小程序
南棱笑笑生25 分钟前
20251027在Ubuntu20.04.6上编译AIO-3576Q38开发板的Buildroot系统解决qt5webengine编译异常的问题
开发语言·qt·rockchip
4Forsee25 分钟前
【Android】View 事件分发机制与源码解析
android·java·前端
沅霖29 分钟前
android kotlin语言中的协程
android·开发语言·kotlin
xiaobobo333031 分钟前
c语言中const关键字和枚举enum的新认识
c语言·开发语言·const·enum
葛小白135 分钟前
C#数据类型:List
开发语言·c#
刘一说37 分钟前
Spring Boot 主程序入口与启动流程深度解析:从 `@SpringBootApplication` 到应用就绪
java·spring boot·后端
合作小小程序员小小店38 分钟前
web开发,在线%蛋糕销售%管理系统,基于asp.net,webform,c#,sql server
开发语言·后端·asp.net·html5·教育电商
小龙报1 小时前
《算法通关指南数据结构和算法篇(3)--- 栈和stack》
开发语言·数据结构·c++·算法·创业创新·学习方法·visual studio
eguid_11 小时前
【从零开始开发远程桌面连接控制工具】01-项目概述与架构设计
java·远程连接·远程控制·远程桌面·vnc·teamviewer