0

0

Java中的Volatile关键字能否保证线程安全?

王林

王林

发布时间:2023-04-21 20:07:06

|

3760人浏览过

|

来源于亿速云

转载

volatile

volatile 是 java 中的一个相对来说比较重要的关键字,主要就是用来修饰会被不同线程访问和修改的变量。

而这个变量只能保证两个特性,一个是保证有序性,另外一个则是保证可见性。

那么什么是有序性,什么又是可见性呢?

有序性

那么什么是有序性呢?

其实程序执行的顺序按照代码的先后顺序执行,禁止进行指令重排序。

立即学习Java免费学习笔记(深入)”;

看似理所当然,其实并不是这样,指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。

但是在多线程环境下,有些代码的顺序改变,有可能引发逻辑上的不正确。

而 volatile 就是因为有这个特性,所以才被大家熟知的。

volatile 又是如何保证有序性的呢?

有很多小伙伴就说,网上说的是 volatile 可以禁止指令指令重排序,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被 volatile 修饰的变量的操作,会严格按照代码顺序执行,就是说当代码执行到 volatile 修饰的变量时,其前面的代码一定执行完毕,后面的代码一定没有执行。

如果这时候,面试官不再继续深挖下去的话,那么恭喜你,可能这个问题已经回答完了,但是如果面试官继续往下深挖,为什么会禁止指令重排,什么又是指令重排呢?

在从源码到指令的执行,一般是分成了三种重排,如图所示:                             

Java中Volatile关键字能不能保证原子性

我们接下来就得看看 volatile 是如何禁止指令重排的。

我们直接用代码来进行验证:

public class ReSortDemo {

int a = 0;
boolean flag = false;

public void mehtod1(){
a = 1;
flag = true;
}

public void method2(){
if(flag){
a = a +1;
System.out.println("最后的值: "+a);
}
}
}

如果有人看到这段代码,肯定会说,那这段代码出来的结果会是什么呢?

有些人说是 2,是的, 如果你只是单线程调用,那结果就是 2,但是如果是多线程调用的时候,最后的输出结果不一定是我们想象到的 2,这时就要把两个变量都设置为 volatile。

如果大家对单例模式了解比较多的话,肯定也是关注过这个 volatile,为什么呢?

大家看看如下代码:

class Singleton {
// 不是一个原子性操作
//private static Singleton instance;
//改进,Volatile 可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
private static volatile Singleton instance;

// 构造器私有化
private Singleton() {
}

// 提供一个静态的公有方法,加入双重检查代码,解决线程安全问题, 同时解决懒加载问题,同时保证了效率, 推荐使用
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

上面的单例模式大家熟悉么?

是的,这就是 **双重检查(DCL 懒汉式) **

有人会说,因为有指令重排序的存在,双端检索机制也也不一定是线程安全的呀,对呀,所以用到了 synchronized 关键字,让他变成了线程安全的了。

可见性

其实可见性就是,在多线程环境中,对共享变量的修改对于其他线程是否立即可见的问题。

Giiso写作机器人
Giiso写作机器人

Giiso写作机器人,让写作更简单

下载

那么他的可见性一般都会表现在什么地方呢?用在什么地方呢?

其实一般用这个变量,很多都是为了保证他的可见性,就比如定义的一个全局变量,在其中有个循环来判断这个变量的值,有一个线程修改了这个参数的时候,这个循环会停止,跳转到之后去执行。

我们来看看没有使用volatile修饰代码实现:

public class Test {

private static boolean flag = false;

public static void main(String[] args) throws Exception{
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程A开始执行:");
for (;;){
if (flag){
System.out.println("跳出循环");
break;
}
}
}
}).start();
Thread.sleep(100);

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程B开始执行");
flag = true;
System.out.println("标识已经变更");
}
}).start();
}
}

结果大家肯定是可想而知,

运行结果肯定是:

线程A开始执行:线程B开始执行标识已经变更

确实,就是这样的。

Java中Volatile关键字能不能保证原子性

如果我们用 volatile 呢,那么这个代码的执行结果就会不一样呢?

我们来试一下:

public class Test {

private static volatile boolean flag = false;

public static void main(String[] args) throws Exception{
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程A开始执行:");
for (;;){
if (flag){
System.out.println("跳出循环");
break;
}
}
}
}).start();
Thread.sleep(100);

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程B开始执行");
flag = true;
System.out.println("标识已经变更");
}
}).start();
}

这样我们就能看到另外一个执行结果,在循环当中的输出语句是可以被执行的。

Java中Volatile关键字能不能保证原子性

也就是说,在线程B 中,我们去修改这个被修饰的变量,那么最终,在线程A中,就能顺利读取到我们的数据信息了。

是否能够保证原子性

不能,我们来看一点代码,被volatile修饰的变量,

public class Test {

// volatile不保证原子性
// 原子性:保证数据一致性、完整性
volatile int number = 0;

public void addPlusPlus() {
number++;
}

public static void main(String[] args) {
Test volatileAtomDemo = new Test();
for (int j = 0; j < 20; j++) {
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
volatileAtomDemo.addPlusPlus();
}
}, String.valueOf(j)).start();
}// 后台默认两个线程:一个是main线程,一个是gc线程
while (Thread.activeCount() > 2) {
Thread.yield();
}
// 如果volatile保证原子性的话,最终的结果应该是20000 // 但是每次程序执行结果都不等于20000
System.out.println(Thread.currentThread().getName() +
" final number result = " + volatileAtomDemo.number);
}
}

如果能够保原子性,那么最终的结果应该是20000,但是每次的最终结果并不能保证就是20000,比如:

main final number result = 17114
main final number result = 20000
main final number result = 19317

三次执行,都是不同的结果,

为什么会出现这种呢?这就和number++有关系了

number++被拆分成3个指令

  • 执行GETFIELD拿到主内存中的原始值number

  • 执行IADD进行加1操作

  • 执行PUTFIELD把工作内存中的值写回主内存中

当多个线程并发执行PUTFIELD指令的时候,会出现写回主内存覆盖问题,所以才会导致最终结果不为 20000,所以 volatile 不能保证原子性。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
全局变量怎么定义
全局变量怎么定义

本专题整合了全局变量相关内容,阅读专题下面的文章了解更多详细内容。

97

2025.09.18

python 全局变量
python 全局变量

本专题整合了python中全局变量定义相关教程,阅读专题下面的文章了解更多详细内容。

106

2025.09.18

c++中volatile关键字的作用
c++中volatile关键字的作用

本专题整合了c++中volatile关键字的相关内容,阅读专题下面的文章了解更多详细内容。

76

2025.10.23

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

766

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

377

2025.12.24

java多线程相关教程合集
java多线程相关教程合集

本专题整合了java多线程相关教程,阅读专题下面的文章了解更多详细内容。

33

2026.01.21

C++多线程相关合集
C++多线程相关合集

本专题整合了C++多线程相关教程,阅读专题下面的的文章了解更多详细内容。

31

2026.01.21

C# 多线程与异步编程
C# 多线程与异步编程

本专题深入讲解 C# 中多线程与异步编程的核心概念与实战技巧,包括线程池管理、Task 类的使用、async/await 异步编程模式、并发控制与线程同步、死锁与竞态条件的解决方案。通过实际项目,帮助开发者掌握 如何在 C# 中构建高并发、低延迟的异步系统,提升应用性能和响应速度。

104

2026.02.06

TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

49

2026.03.13

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.4万人学习

C# 教程
C# 教程

共94课时 | 11.4万人学习

Java 教程
Java 教程

共578课时 | 82.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号