文章目录
概述
在 Java 中,volatile 和 synchronized 是两种重要的并发控制机制,它们用于保证多线程环境下的数据一致性。volatile 用于声明一个变量的可见性和禁止指令重排,而 synchronized 用于确保代码块或方法的互斥访问。
本文将深入探讨这两个关键字的底层实现原理和技术细节。
volatile 的实现原理
基本概念
volatile 关键字用于声明一个变量在多线程环境下的可见性。当一个线程修改了一个被声明为 volatile 的变量后,这个修改对其他线程是立即可见的。此外,volatile 还禁止了编译器和处理器对这些变量的指令重排,从而确保了内存访问的顺序性。
作用
- 可见性(Visibility): 当一个线程修改了 volatile 变量的值时,这个变量的新值会立即被写回主内存,而其他线程会立即看到这个新值。这确保了所有线程对共享变量的修改都是可见的,避免了一个线程修改了变量值而其他线程不知道的情况。
- 禁止指令重排序: volatile 关键字禁止了指令重排序,确保了一些关键操作的执行顺序。在没有 volatile 的情况下,编译器和处理器可能会对指令进行重排序,导致多线程环境下的程序出现不可预期的错误。
- 保证原子性(Atomicity): 尽管 volatile 不能保证复合操作的原子性,但它确保了对单个变量的读/写操作是原子的。这意味着一个线程在写入 volatile 变量时,其他线程不能同时进行写操作,从而避免了竞态条件。
虽然 volatile 提供了一定程度上的线程安全性,但它并不能解决所有的并发问题。对于一些复合操作(例如检查-更新操作),仍然需要额外的同步手段,例如使用 synchronized 关键字或 java.util.concurrent 包提供的工具类。
底层实现细节
字节码层面
- 创建一个新的Java类,并声明一个volatile变量。
package org.hbin.bytecode;
/**
* @author Haley
* @version 1.0
* 2024/8/22
*/
public class VolatileTest {
volatile int num;
}
- 编译这个类
- 使用反汇编工具查看class文件内容
- 对照上述字段访问标志表和源码分析,我们可以得知字节码文件中的该变量访问标志为0x0040,即ACC_VOLATILE。
JVM层面
volatile的底层实现跟JMM息息相关,我们先了解下JMM是什么?
JMM
《Java虚拟机规范》中曾试图定义一种“Java内存模型”(Java Memory Model,简称JMM)来屏蔽各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果。
Java内存模型是一种抽象的概念,并不真实存在,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。
简单来说:在JMM中,每个线程都有自己的工作内存(也称为本地内存),它存储着线程私有的数据副本,包括栈帧、程序计数器等。而主内存则是所有线程共享的内存区域,它存储着所有的共享变量。
当线程访问共享变量时,它首先会把共享变量从主内存中读取到自己的工作内存中进行操作,包括读取、修改和写入。然后,线程对变量的操作完成后,必须将结果刷新到主内存中,以便其他线程可以看到最新的值。这个过程可以看作是线程和主内存之间的数据同步。
关于主内存与工作内存之间具体的交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步回主内存之类的实现细节,Java内存模型中定义了以下8种操作来完成,虚拟机实现时必须保证下面的每一种操作都是原子的、不可再分的(对于double和long类型的变量有例外,别说。)
操作 |
---|