吊打面试官(五)--Java关键字volatile一文全掌握
yund56 2025-07-28 23:31 23 浏览
前言
volatile 是 Java 中的一个关键字,用于声明变量。当一个变量被声明为 volatile时,它可以确保线程对这个变量的读写都是直接从主内存中进行的。这也是面试官最爱问的点,接下来我们详细介绍这个关联字各个方面。
volatile关键字使用详细介绍
1. 可见性
当一个线程修改了一个 volatile变量的值,其他线程能够立即看到修改后的值。
这是因为 volatile变量不会被缓存在寄存器或其他处理器不可见的地方,因此保证了每次读取 volatile变量都会从主内存中读取最新的值。
2. 有序性
volatile变量的读写操作具有一定的有序性,即禁止了指令重排序优化,就是禁止编译器自动重新排序。
这意味着,在一个线程中,对 volatile变量的写操作一定发生在后续对这个变量的读操作之前。
3. 使用场景
volatile 关键字通常用于以下场景:* 当多个线程共享一个变量,并且至少有一个线程会修改这个变量时。* 当需要确保变量的修改对所有线程立即可见时。* 当变量的状态不需要依赖于之前的值,或者不需要与其他状态变量共同参与不变约束时。
4. 代码示例
下面是一个使用 volatile 关键字的简单示例:
public class VolatileExample {
private volatile boolean flag = false;
public void setFlag(boolean flag) {
this.flag = flag;
}
public boolean getFlag() {
return this.flag;
}
public static void main(String[] args) {
VolatileExample example = new VolatileExample();
// 线程1:修改flag的值
new Thread(() -> {
try {
Thread.sleep(1000); // 模拟一些操作
} catch (InterruptedException e) {
e.printStackTrace();
}
example.setFlag(true);
System.out.println("Flag被设置为true");
}).start();
// 线程2:检查flag的值
new Thread(() -> {
while (!example.getFlag()) {
// 忙等待,直到flag变为true
}
System.out.println("检测到Flag为true");
}).start();
}
}在这个示例中,我们有两个线程。
线程1在休眠1秒后将 `flag` 设置为 `true`,而线程2则不断检查 `flag` 的值,直到它变为 `true`。由于 `flag` 被声明为 volatile,因此线程2能够立即看到线程1对 `flag` 的修改,并退出循环。
5.使用注意事项*
volatile关键字不能保证原子性。如果需要对变量进行复合操作(例如自增),则应该使用 `synchronized` 关键字或其他并发工具(如 `AtomicInteger`)来确保线程安全。
* 过度使用 volatile可能会导致性能下降,因为它会禁止编译器和处理器对代码进行某些优化。因此,在使用 volatile时应该仔细考虑其必要性。
volatile关键字使用场景举例
1.状态标志位
在多线程程序中, volatile 关键字用于表示一个状态标志位,例如程序运行状态或中断使能状态。这些状态标志位通常会被多个线程访问和修改,使用 volatile 可以确保它们的可见性和有序性。使用 volatile 关键字可以防止线程间的数据不一致性问题,确保每个线程都能看到最新的状态标志位值。这对于控制线程行为和同步操作非常关键。
代码举例:
public class VolatileExample {
private volatile boolean flag = false;
public void startTask() {
new Thread(() -> {
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
System.out.println("Flag has been set to true.");
}).start();
}
public void monitorTask() {
new Thread(() -> {
while (!flag) {
// 循环等待,直到flag变为true
}
System.out.println("Flag is now true. Task can proceed.");
}).start();
}
public static void main(String[] args) {
VolatileExample example = new VolatileExample();
example.startTask();
example.monitorTask();
}
}2.单例模式的双重检查锁
在单例模式中, volatile 关键字用于确保单例实例在多线程环境下的唯一性和可见性。通过将实例声明为 volatile ,可以防止线程在读取和写入实例时看到不一致的值。在多线程环境中, volatile 关键字可以防止指令重排序,确保单例实例的初始化操作在所有线程中都完成,从而避免潜在的线程安全问题。
代码举例:
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}3.线程安全的计数器
volatile 关键字也适用于简单的计数器,如统计某个事件的发生次数。虽然 volatile 不能保证复合操作的原子性,但它可以确保每次读取和写入操作都是对主内存的访问。在需要统计事件发生次数的场景中, volatile 关键字可以确保计数的准确性,防止线程在读取和写入计数器时看到不一致的值。
代码举例:
public class VolatileCounter {
private volatile int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
public static void main(String[] args) {
VolatileCounter counter = new VolatileCounter();
// 启动多个线程进行计数
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
}).start();
}
// 等待所有线程完成
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final count: " + counter.getCount());
}
}4.直接访问硬件寄存器
在嵌入式系统编程中,直接与硬件设备交互时,使用 volatile 可以确保每次读写操作都直接从内存中进行,而不是使用寄存器缓存中的值。这可以避免编译器对寄存器访问的优化,确保与硬件的交互是准确的。在嵌入式系统编程中, volatile 关键字的作用至关重要。它可以防止编译器对硬件寄存器访问的优化,确保每次读写操作都是对实际硬件的访问,从而提高系统的稳定性和可靠性。
使用JNI作为代码举例:
Java代码:
public class HardwareAccess {
// 声明本地方法
public native int readRegister();
// 加载动态链接库
static {
System.loadLibrary("hardware");
}
public static void main(String[] args) {
HardwareAccess ha = new HardwareAccess();
int registerValue = ha.readRegister();
System.out.println("Register value: " + Integer.toHexString(registerValue));
}
}
生成JNI头文件:
javac HardwareAccess.java
javac -h . HardwareAccess.java
编写C代码实现本地方法:
#include <jni.h>
#include "HardwareAccess.h"
#include <stdio.h>
JNIEXPORT jint JNICALL Java_HardwareAccess_readRegister(JNIEnv *env, jobject obj) {
// 模拟读取寄存器数据
printf("Reading register...\n");
int simulatedRegisterValue = 0x1234;
return simulatedRegisterValue;
}
编译C代码为动态链接库:
Linux:gcc -shared -fpic -o libhardware.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux HardwareAccess.c
运行Java程序:
java -Djava.library.path=. HardwareAccess5.中断处理程序中的标志位
在处理中断时,通常需要对中断标志进行读写操作。使用 volatile 可以确保中断处理程序对标志位的修改能够立即被其他线程看到,从而确保中断处理的正确性。在中断处理程序中使用 volatile 关键字可以确保中断标志位的修改对所有线程立即可见,避免因中断处理导致的线程间数据不一致问题。
代码举例:
public class InterruptExample {
private volatile boolean interrupted = false;
public void run() {
while (!interrupted) {
// 执行一些任务
}
// 线程被中断,执行清理操作
}
public void setInterrupted() {
interrupted = true;
}
public static void main(String[] args) {
InterruptExample example = new InterruptExample();
Thread thread = new Thread(example::run);
thread.start();
// 模拟中断线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
example.setInterrupted();
}
}6.信号处理程序中的标志位
在信号处理程序中,使用 volatile 可以确保信号处理程序对标志位的修改能够立即被其他线程看到,从而确保信号处理的正确性。
在信号处理程序中使用 volatile 关键字可以防止指令重排序,确保信号处理程序对标志位的修改对所有线程立即可见,从而提高信号处理的可靠性和稳定性。
信号处理通常通过 sun.misc.Signal 和 sun.
misc.SignalHandler 来实现,但需要注意的是,这些类并不是Java标准API的一部分,可能在不同的JDK实现中有所不同。
代码举例:
import sun.misc.Signal;
import sun.misc.SignalHandler;
public class SignalExample {
private volatile boolean signalReceived = false;
public void handleSignal() {
Signal.handle(new Signal("INT"), new SignalHandler() {
@Override
public void handle(Signal signal) {
signalReceived = true;
}
});
}
public void run() {
while (!signalReceived) {
// 执行一些任务
}
// 信号处理程序已设置标志位,执行清理操作
}
public static void main(String[] args) {
SignalExample example = new SignalExample();
example.handleSignal();
new Thread(example::run).start();
// 模拟发送信号
try {
Thread.sleep(1000);
Signal.raise(new Signal("INT"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}7.防止优化编译器优化
在某些情况下,编译器可能会对代码进行优化,导致变量的值在多线程环境中不一致。使用 volatile 可以防止这种优化,确保每次访问变量时都从内存中读取最新的值。在需要防止编译器优化的场景中, volatile 关键字可以确保变量的值始终是最新的,避免因编译器优化导致的线程间数据不一致问题。
代码举例:
public class OptimizationExample {
private volatile int counter = 0;
public void increment() {
counter++;
}
public int getCounter() {
return counter;
}
public static void main(String[] args) {
OptimizationExample example = new OptimizationExample();
// 启动多个线程进行计数
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
example.increment();
}
}).start();
}
// 等待所有线程完成
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final counter value: " + example.getCounter());
}
}在这个示例中, volatile 关键字确保了对 counter 变量的每次访问都直接从内存中进行,而不是使用寄存器缓存中的值,从而防止了编译器优化导致的不一致问题。
在多线程编程中,volatile关键字与synchronized关键字有何不同?
volatile 关键字
可见性:
volatile 确保了变量的修改对所有线程是可见的。当一个线程修改了 volatile 变量的值,这个变化会立即被写入主内存,而其他线程在读取该变量时会从主内存中获取最新的值。
禁止指令重排序:
volatile 可以防止编译器和处理器对代码进行优化,确保指令按照程序的顺序执行。
适用场景:
volatile 适用于那些被多个线程访问但并不涉及复合操作(例如递增操作)的变量。它主要用于状态标志、控制变量等场景。
性能:
由于 volatile 不需要使用锁,因此它的性能开销相对较小。
synchronized关键字
互斥性:
synchronized 确保同一时刻只有一个线程可以访问被保护的代码块或方法,从而避免了多个线程之间的竞争条件。
有序性:
synchronized 通过锁定和解锁机制,隐式地保证了代码执行的顺序性。
适用场景:
synchronized 适用于需要保证原子性和线程安全性的场景,例如对共享资源的读写操作。
性能:
synchronized 可能会带来较大的性能开销,因为它涉及到线程的阻塞和唤醒,以及上下文切换。总的来说, volatile 和 synchronized 在多线程编程中各有其独特的用途。 volatile 适用于需要保证变量可见性且不涉及复杂操作的场景,而 synchronized 则适用于需要保证代码块或方法原子性和有序性的场景。
volatile关键字在Java中的实现机制是什么?
volatile 关键字在Java中的实现机制主要涉及Java内存模型(JMM)、内存屏障(Memory Barrier)和缓存一致性协议(如MESI协议)。
Java内存模型(JMM):
主内存与工作内存:
JMM定义了线程如何与主内存和线程本地内存交互。主内存是所有线程共享的内存区域,存储所有变量的值。每个线程有自己的本地内存,存储从主内存中读取的变量副本。
内存可见性:当一个线程修改了 volatile 变量的值,这个修改会立即刷新到主内存,并通知其他线程更新缓存。其他线程在读取该变量时,会从主内存中重新加载最新的值。
内存屏障(Memory Barrier):
写屏障(Store Barrier):
在写操作之后插入,确保写操作对其他线程可见。
读屏障(Load Barrier):在读操作之前插入,确保读操作从主内存中加载最新值。volatile 通过插入内存屏障来禁止指令重排序,确保变量的读写操作按照程序的顺序执行。
缓存一致性协议(MESI协议):
MESI协议:现代CPU通常有多级缓存(L1、L2、L3),为了保证缓存一致性,CPU使用缓存一致性协议(如MESI协议)。
volatile 写操作会触发缓存行状态变为Modified,并强制将数据写回主内存; volatile 读操作会触发缓存行状态变为Shared,并从主内存中加载最新数据。
JVM层面的实现:
字节码层面的实现:在字节码层面, volatile 变量的读写操作会被标记为ACC_VOLATILE标志。JVM在执行这些操作时会插入内存屏障。
JIT编译器的优化:JIT编译器在生成机器码时,会根据 volatile 的语义插入内存屏障。例如,在x86架构下, volatile 写操作会插入StoreLoad屏障,确保写操作对其他线程可见。
硬件层面的实现:
x86架构:在x86架构下, volatile 写操作会使用LOCK前缀指令,强制将数据写回主内存,并通知其他CPU缓存失效。
ARM架构:在ARM架构下, volatile 通过内存屏障指令(如DMB)来实现。通过上述机制, volatile 关键字确保了多线程环境下变量的可见性和有序性,从而避免了由于线程间数据不一致导致的问题。
volatile关键字可能导致性能下降问题
volatile关键字在以下情况下可能会导致性能下降:
1. 缓存行争用:
当多个线程同时访问被volatile修饰的变量时,可能会导致缓存行争用。这是因为每个处理器都有自己的缓存,当多个线程访问同一个缓存行中的数据时,可能会导致缓存失效,从而需要从主内存中重新加载数据。这种缓存失效和重新加载的过程会增加访问延迟,从而降低性能。
2. 内存屏障开销:
volatile关键字会引入内存屏障,以确保变量的修改对所有线程都是可见的。内存屏障是一种特殊的指令,用于在编译器和处理器之间同步内存访问顺序。虽然内存屏障可以确保正确的内存可见性,但它也可能导致性能下降,因为它会限制编译器和处理器对指令进行重排序的能力。
3. 禁止编译器优化:
volatile关键字禁止编译器对变量进行优化,以确保每次访问该变量时都能获取到最新的值。这可能会导致生成的代码相对较多,从而影响程序性能。
4. 原子操作开销:
volatile关键字可以确保对变量的读取和写入都是原子的,这意味着它们不会被其他线程的操作中断。原子操作本身可能比非原子操作更昂贵,因为它们需要额外的处理器资源来保证操作的完整性。尽管volatile关键字可能会导致性能下降,但在许多情况下,这种影响是可以接受的。例如,当多个线程需要共享一个简单的状态变量(如计数器)时,使用volatile关键字可以确保所有线程都能看到最新的值,而不会引入不必要的复杂性或性能开销。
前言
volatile 是 Java 中的一个关键字,用于声明变量。本文我们讲述了它的详细使用场景,典型使用案例,和synchronized关键字的对比,它的实现原理,性能问题等。基本覆盖了它涉及的各个方面,请各位看官自行取用。
求关注哦
相关推荐
- SM小分队Girls on Top,女神战队少了f(x)?
-
这次由SM娱乐公司在冬季即将开演的smtown里,将公司的所有女团成员集结成了一个小分队project。第一位这是全面ACE的大姐成员权宝儿(BoA),出道二十年,在日本单人销量过千万,韩国国内200...
- 韩国女团 aespa 首场 VR 演唱会或暗示 Quest 3 将于 10 月推出
-
AmazeVR宣布将在十月份举办一场现场VR音乐会,观众将佩戴MetaQuest3进行体验。韩国女团aespa于2020年11月出道,此后在日本推出了三张金唱片,在韩国推出了...
- 韩网热议!女团aespa成员Giselle在长腿爱豆中真的是legend
-
身高163的Giselle,长腿傲人,身材比例绝了...
- 假唱而被骂爆的女团:IVE、NewJeans、aespa上榜
-
在韩国,其实K-pop偶像并不被认为是真正的歌手,因为偶像们必须兼备舞蹈能力、也经常透过对嘴来完成舞台。由于科技的日渐发达,也有许多网友会利用消音软体来验证K-pop偶像到底有没有开麦唱歌,导致假唱这...
- 新女团Aespa登时尚大片 四个少女四种style
-
来源:环球网
- 韩国女团aespa新歌MV曝光 画面梦幻造型超美
-
12月20日,韩国女团aespa翻唱曲《DreamsComeTrue》MV公开,视频中,她们的造型超美!WINTER背后长出一双梦幻般的翅膀。柳智敏笑容甜美。宁艺卓皮肤白皙。GISELLE五官精致...
- 女网友向拳头维权,自称是萨勒芬妮的原型?某韩国女团抄袭KDA
-
女英雄萨勒芬妮(Seraphine)是拳头在2020年推出的第五位新英雄,在还没有正式上线时就备受lsp玩家的关注,因为她实在是太可爱了。和其他新英雄不同的是,萨勒芬妮在没上线时就被拳头当成虚拟偶像来...
- 人气TOP女团是?INS粉丝数见分晓;TWICE成员为何在演唱会落泪?
-
现在的人气TOP女团是?INS粉丝数见分晓!现在爱豆和粉丝之间的交流方法变得多种多样,但是Instagram依然是主要的交流手段。很多粉丝根据粉丝数评价偶像的人气,拥有数百、数千万粉丝的组合作为全球偶...
- 韩国女团MVaespa Drama MV_韩国女团穿超短裙子跳舞
-
WelcometoDrama.Pleasefollow4ruleswhilewatchingtheDrama.·1)Lookbackimmediatelywhenyoufe...
- aespa师妹团今年将出道! SM职员亲口曝「新女团风格、人数」
-
记者刘宛欣/综合报导南韩造星工厂SM娱乐曾打造出东方神起、SUPERJUNIOR、少女时代、SHINee、EXO等传奇团体,近年推出的aespa、RIIZE更是双双成为新生代一线团体,深受大众与粉丝...
- 南韩最活跃的女团aespa,新专辑《Girls》即将发布,盘点昔日经典
-
女团aespa歌曲盘点,新专辑《Girls》即将发布,期待大火。明天也就是2022年的7月8号,aespa新专辑《Girls》即将发行。这是继首张专辑《Savage》之后,时隔19个月的第二张专辑,这...
- 章泽天女团aespa出席戛纳晚宴 宋康昊携新片亮相
-
搜狐娱乐讯(山今/文玄反影/图科明/视频)法国时间5月23日晚,女团aespa、宋康昊、章泽天等明星亮相戛纳晚宴。章泽天身姿优越。章泽天肩颈线优越。章泽天双臂纤细。章泽天仪态端正。女团aespa亮...
- Aespa舞台暴露身高比例,宁艺卓脸大,柳智敏有“TOP”相
-
作为SM公司最新女团aespa,初舞台《BlackMamba》公开,在初舞台里,看得出来SM公司是下了大功夫的,虽然之前SM公司新出的女团都有很长的先导片,但是aespa显然是有“特殊待遇”。运用了...
- AESPA女团成员柳智敏karina大美女
-
真队内速度最快最火达成队内首个且唯一两百万点赞五代男女团中输断层第一(图转自微博)...
- 对来学校演出的女团成员语言性骚扰?韩国这所男高的学生恶心透了
-
哕了……本月4日,景福男子高中相关人士称已经找到了在SNS中上传对aespa成员进行性骚扰文章的学生,并开始着手调查。2日,SM娱乐创始人李秀满的母校——景福高中迎来了建校101周年庆典活动。当天,S...
- 一周热门
- 最近发表
-
- SM小分队Girls on Top,女神战队少了f(x)?
- 韩国女团 aespa 首场 VR 演唱会或暗示 Quest 3 将于 10 月推出
- 韩网热议!女团aespa成员Giselle在长腿爱豆中真的是legend
- 假唱而被骂爆的女团:IVE、NewJeans、aespa上榜
- 新女团Aespa登时尚大片 四个少女四种style
- 韩国女团aespa新歌MV曝光 画面梦幻造型超美
- 女网友向拳头维权,自称是萨勒芬妮的原型?某韩国女团抄袭KDA
- 人气TOP女团是?INS粉丝数见分晓;TWICE成员为何在演唱会落泪?
- 韩国女团MVaespa Drama MV_韩国女团穿超短裙子跳舞
- aespa师妹团今年将出道! SM职员亲口曝「新女团风格、人数」
- 标签列表
-
- filter函数js (37)
- filter函数excel用不了 (73)
- 商城开发 (40)
- 影视网站免费源码最新版 (57)
- 影视资源api接口 (46)
- 网站留言板代码大全 (56)
- java版软件下载 (52)
- java教材电子课本下载 (48)
- 0基础编程从什么开始学 (50)
- java是用来干嘛的 (51)
- it入门应该学什么 (55)
- java线上课程 (55)
- 学java的软件叫什么软件 (38)
- 程序开发软件有哪些 (53)
- 软件培训 (59)
- 机器人编程代码大全 (50)
- 少儿编程教程免费 (45)
- 新代系统编程教学 (61)
- 共创世界编程网站 (38)
- 亲测源码 (36)
- 三角函数积分公式表 (35)
- 函数的表示方法 (34)
- 表格乘法的公式怎么设置 (34)
- sumif函数的例子 (34)
- 图片素材 (36)
