Java多线程三大特性之原子性、可见性、有序性详解

 更新时间:2026年07月13日 10:05:19   作者:奋进的芋圆  
在并发编程中有三个非常重要的特性,分别是原子性、有序性,、可见性,这篇文章主要介绍了Java多线程三大特性之原子性、可见性、有序性的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下

——含完整代码示例与底层原理剖析

文档目标:系统掌握 Java 并发三大核心特性的实现机制、使用场景及底层原理

一、引言:为什么需要三大特性?

在多线程环境中,多个线程共享内存,若不加控制,会出现以下问题:

  • 原子性破坏i++ 操作被拆分为“读-改-写”,导致结果错误;
  • 可见性缺失:线程 A 修改变量,线程 B 仍读取旧值;
  • 有序性混乱:编译器/CPU 指令重排序导致逻辑错乱(如 DCL 单例失效)。

Java 内存模型(JMM)通过 原子性(Atomicity)、可见性(Visibility)、有序性(Ordering) 三大特性保障并发安全。

⚠️ 注意:“一致性”不是 Java 并发三大特性的标准术语,常见于数据库(ACID)或分布式系统(CAP)。

二、三大特性定义

特性定义核心问题
原子性操作不可分割,要么全部执行成功,要么完全不执行复合操作被线程交错打断
可见性一个线程修改共享变量后,其他线程能立即看到最新值工作内存与主内存不同步
有序性程序执行顺序应符合代码编写顺序编译器/CPU 指令重排序

三、原子性(Atomicity)

1.synchronized—— JVM 内置锁

代码示例

public class SynchronizedExample {
    private int count = 0;

    public synchronized void increment() {
        count++; // 非原子操作,需同步保护
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedExample e = new SynchronizedExample();
        Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) e.increment(); });
        Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) e.increment(); });
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println("Final count: " + e.count); // 输出 2000
    }
}

底层原理

  • Monitor 机制:每个 Java 对象关联一个 Monitor(C++ 实现),由 JVM 管理。

  • 对象头 Mark Word:存储锁状态(无锁/偏向锁/轻量级锁/重量级锁)。

  • 字节码指令 :

    • monitorenter:进入同步块时获取锁;
    • monitorexit:退出时释放锁。
  • 锁升级路径 :

    无锁 → 偏向锁(单线程优化) → 轻量级锁(自旋) → 重量级锁(OS 阻塞)
  • 内存语义:进入/退出同步块时自动刷新工作内存 ↔ 主内存,同时保证可见性与有序性

2.ReentrantLock—— 显式可重入锁

代码示例

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {
    private int count = 0;
    private final ReentrantLock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock(); // 必须在 finally 中释放
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockExample e = new ReentrantLockExample();
        Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) e.increment(); });
        Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) e.increment(); });
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println("Final count: " + e.count); // 输出 2000
    }
}

底层原理

  • AQS(AbstractQueuedSynchronizer) :

    • 核心字段:volatile int state(锁状态)、Node head/tail(CLH 双向等待队列)。
  • 加锁流程:

    1. CAS 尝试将 state 从 0 → 1;
    2. 失败则封装线程为 Node 入队,并调用 LockSupport.park() 阻塞;
  • 解锁流程 :

    1. CAS 将 state 减 1;
    2. 唤醒队列头节点(LockSupport.unpark())。
  • 可重入性:记录当前持有线程,同一线程可多次加锁(state 递增)。

  • 优势:支持公平锁、超时、可中断、多条件变量(Condition)。

3. 原子类(如AtomicInteger)

代码示例

import java.util.concurrent.atomic.AtomicInteger;

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

    public void increment() {
        count.incrementAndGet(); // 原子自增
    }

    public static void main(String[] args) throws InterruptedException {
        AtomicExample e = new AtomicExample();
        Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) e.increment(); });
        Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) e.increment(); });
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println("Final count: " + e.count.get()); // 输出 2000
    }
}

底层原理

  • CAS(Compare-And-Swap) :

    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }
    
  • Unsafe 类:调用 native 方法 compareAndSwapInt(),利用 CPU 原子指令(如 x86 的 LOCK CMPXCHG)。

  • 无锁并发:失败则自旋重试,避免线程阻塞开销。

  • volatile 修饰:内部字段 private volatile int value天然具备可见性与部分有序性

  • ABA 问题:可通过 AtomicStampedReference 解决。

4.StampedLock—— 乐观读写锁

代码示例

import java.util.concurrent.locks.StampedLock;

public class StampedLockExample {
    private double x = 0, y = 0;
    private final StampedLock sl = new StampedLock();

    void move(double dx, double dy) {
        long stamp = sl.writeLock();
        try {
            x += dx;
            y += dy;
        } finally {
            sl.unlockWrite(stamp);
        }
    }

    double distanceFromOrigin() {
        long stamp = sl.tryOptimisticRead(); // 乐观读
        double currentX = x, currentY = y;
        if (!sl.validate(stamp)) { // 检查是否被写入
            stamp = sl.readLock(); // 升级为悲观读
            try {
                currentX = x;
                currentY = y;
            } finally {
                sl.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }

    public static void main(String[] args) {
        StampedLockExample obj = new StampedLockExample();
        obj.move(3, 4);
        System.out.println("Distance: " + obj.distanceFromOrigin()); // 输出 5.0
    }
}

底层原理

  • 改进型 AQS:使用 long stamp 代替 int state,低 7 位表示锁模式,高位为版本号。

  • 乐观读 :

    • 不加锁,仅记录版本号;
    • 读取后验证版本是否变化(validate(stamp));
    • 若未被写入,则读成功;否则升级为悲观读。
  • 写锁与读锁互斥,但多个乐观读可并发。

  • 不支持重入,且无条件变量。

  • 适用场景:读远多于写的高性能计算(如几何坐标计算)。

四、可见性(Visibility)

1.volatile—— 轻量级可见性保障

代码示例

public class VolatileVisibilityExample {
    private volatile boolean running = true; // volatile 保证可见性

    public void stop() {
        running = false;
    }

    public void runTask() {
        while (running) {
            System.out.println("Working...");
            try { Thread.sleep(500); } catch (InterruptedException e) { break; }
        }
        System.out.println("Task stopped.");
    }

    public static void main(String[] args) throws InterruptedException {
        VolatileVisibilityExample task = new VolatileVisibilityExample();
        Thread worker = new Thread(task::runTask);
        worker.start();
        Thread.sleep(2000);
        task.stop(); // 主线程停止任务
        worker.join();
    }
}

❗ 若去掉 volatileworker 线程可能永远看不到 running = false,导致死循环。

底层原理

  • JMM 内存语义:

    • 写 volatile:立即刷回主内存,并使其他 CPU 缓存行失效;
    • 读 volatile:从主内存重新加载最新值。
  • 内存屏障(Memory Barrier):

    • 写屏障StoreStore + StoreLoad,禁止写之前的操作重排到写之后;
    • 读屏障LoadLoad + LoadStore,禁止读之后的操作重排到读之前。
  • 缓存一致性协议:如 MESI 协议,确保多核 CPU 缓存一致性。

  • 不保证原子性:如 i++ 仍需锁或原子类。

2.synchronized保证可见性

代码示例(复用前文SynchronizedExample)

底层原理

  • happens-before 规则:解锁 happens-before 后续加锁。

  • 内存同步:

    • 进入同步块:清空工作内存,从主内存重新加载共享变量;
    • 退出同步块:将修改后的共享变量 flush 到主内存。
  • 底层通过 Monitor 的 entry/exit 操作触发内存同步。

3.final字段的可见性

代码示例

public class FinalFieldExample {
    private final int x;
    private final int y;

    public FinalFieldExample(int x, int y) {
        this.x = x;
        this.y = y; // 构造完成后对所有线程可见
    }

    public int sum() {
        return x + y; // 无需同步,安全
    }

    public static void main(String[] args) {
        FinalFieldExample obj = new FinalFieldExample(3, 4);
        new Thread(() -> {
            System.out.println("Sum in thread: " + obj.sum()); // 输出 7
        }).start();
    }
}

底层原理

  • JLS §17.5 final 语义:

    • 构造函数内对 final 字段的写操作,不会被重排到构造函数之外
    • 构造完成后,final 字段的值对所有线程立即可见
  • 安全发布:只要对象正确构造(未逸出),final 字段无需同步即可安全读取。

  • 适用场景:构建不可变对象(Immutable Object)。

五、有序性(Ordering)

1.volatile禁止重排序(DCL 单例)

代码示例

public class DoubleCheckedLockingSingleton {
    // 必须加 volatile!否则可能返回未完全初始化的对象
    private volatile static DoubleCheckedLockingSingleton instance;

    private DoubleCheckedLockingSingleton() {}

    public static DoubleCheckedLockingSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedLockingSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedLockingSingleton(); // 关键:防止重排序
                }
            }
        }
        return instance;
    }

    public void doSomething() {
        System.out.println("Singleton working...");
    }

    public static void main(String[] args) {
        DoubleCheckedLockingSingleton s1 = getInstance();
        DoubleCheckedLockingSingleton s2 = getInstance();
        System.out.println(s1 == s2); // true
        s1.doSomething();
    }
}

底层原理

  • 对象创建三步骤:

    1. 分配内存空间;
    2. 初始化对象;
    3. instance 指向内存地址。
  • 重排序风险:步骤 2 与 3 可能重排为 1 → 3 → 2,导致其他线程拿到未初始化对象。

  • volatile 作用:

    • instance = new ... 写操作后插入 StoreStore 屏障
    • 确保初始化完成后再赋值引用。
  • happens-before:volatile 写 happens-before 后续 volatile 读。

2.synchronized保证有序性

代码示例(复用前文)

底层原理

  • 程序顺序规则:同步块内部代码按程序顺序执行。
  • 监视器锁规则:解锁 happens-before 后续加锁,建立跨线程顺序。
  • 底层 Monitor 机制隐含内存屏障,禁止临界区内外的操作重排。

六、综合对比表

方案原子性可见性有序性底层机制适用场景
synchronizedMonitor(对象头 Mark Word)通用同步,复合操作
ReentrantLockAQS + CAS + CLH 队列需要高级控制(超时、公平等)
原子类✅(单变量)CAS + volatile + Unsafe单变量无锁并发
StampedLock改进 AQS + 乐观读读多写少高性能场景
volatile✅(部分)内存屏障 + 缓存一致性协议状态标志、DCL 单例
final✅(构造后)✅(构造阶段)JMM final 语义不可变对象

七、最佳实践建议

  • 状态标志volatile boolean flag
  • 计数器/累加器AtomicIntegerLongAdder(高并发分段累加)
  • 复合操作(如转账)synchronized(简单)或 ReentrantLock(灵活)
  • 高性能读多写少StampedLock
  • 不可变对象final 字段 + 构造函数安全初始化
  • 避免过度同步:锁粒度越小越好,优先无锁方案

八、附录:关键概念速查

概念说明
JMM(Java Memory Model)定义线程与主内存交互规则
happens-beforeJMM 核心规则,保证操作可见性与有序性
CAS(Compare-And-Swap)CPU 原子指令,无锁并发基础
AQSJUC 锁框架基石,基于 CLH 队列
内存屏障禁止指令重排序的硬件/编译器指令

💡 口诀记忆原(原子性)可(可见性)有(有序性)——Java 并发三基石。

到此这篇关于Java多线程三大特性之原子性、可见性、有序性详解的文章就介绍到这了,更多相关Java多线原子性、可见性、有序性内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 深入理解Java8新特性之接口中的默认方法和静态方法

    深入理解Java8新特性之接口中的默认方法和静态方法

    从Java8开始,程序允许在接口中包含带有具体实现的方法,使用default修饰,这类方法就是默认方法。默认方法在接口中可以添加多个,并且Java8提供了很多对应的接口默认方法,接下来让我们一起来看看吧
    2021-11-11
  • 浅谈java泛型的作用及其基本概念

    浅谈java泛型的作用及其基本概念

    下面小编就为大家带来一篇浅谈java泛型的作用及其基本概念。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-08-08
  • Java Swing JProgressBar进度条的实现示例

    Java Swing JProgressBar进度条的实现示例

    这篇文章主要介绍了Java Swing JProgressBar进度条的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • Java 详解异常的处理机制

    Java 详解异常的处理机制

    异常是程序中的一些错误,但并不是所有的错误都是异常,并且错误有时候是可以避免的。比如你的代码少一个分号,那运行出来结果是提示是错误 java.lang.Error;如果你用System.out.println(11/0),那你是因为用0做了除数,会抛出 java.lang.ArithmeticException 的异常
    2021-11-11
  • SpringBoot将所有依赖(包括本地jar包)打包到项目

    SpringBoot将所有依赖(包括本地jar包)打包到项目

    这篇文章主要介绍了SpringBoot将所有依赖(包括本地jar包)打包到项目,本文通过示例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-06-06
  • Eclipse2020安装了最新版本的JDK却无法打开的问题

    Eclipse2020安装了最新版本的JDK却无法打开的问题

    这篇文章主要介绍了Eclipse2020安装了最新版本的JDK却无法打开,提示版本太老的完美解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • SpringBoot前端传递数组后端接收两种常用的方法

    SpringBoot前端传递数组后端接收两种常用的方法

    这篇文章主要给大家介绍了关于SpringBoot前端传递数组后端接收两种常用的方法,文中通过代码介绍的非常详细,对大家的学习或者工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-04-04
  • Spring Boot 自定义数据源DruidDataSource代码

    Spring Boot 自定义数据源DruidDataSource代码

    这篇文章主要介绍了Spring Boot 自定义数据源DruidDataSource代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • Spark Streaming 内部运行机制示例详解

    Spark Streaming 内部运行机制示例详解

    这篇文章主要介绍了Spark Streaming 内部运行机制示例详解,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-05-05
  • 值得收藏的SpringBoot 实用的小技巧

    值得收藏的SpringBoot 实用的小技巧

    最近分享的一些源码、框架设计的东西。我发现大家热情不是特别高,想想大多数应该还是正儿八经写代码的居多;这次就分享一点接地气的: SpringBoot 使用中的一些小技巧 ,需要的朋友可以参考下
    2018-10-10

最新评论