这篇文章主要介绍了多方面解读Java中的volatile关键字,它的作用是强制对被修饰的变量的写操作立即刷新到主存中,并强制对该变量的读操作从主存中读取最新的值,而不是使用缓存中的值,需要的朋友可以参考下
介绍
作用
保证变量的可见性:
禁止指令重排:
不能保证原子性
可见性、有序性、原子性
不会导致线程阻塞
使用场景
实现原理
happens-before
局限性
和 synchronized 关键字比较
和 Atomic 类比较
和 final 关键字比较
和 ThreadLocal 关键字比较
失效场景
测试
介绍
volatile 是 Java 中的关键字,用于修饰变量。它的作用是强制对被修饰的变量的写操作立即刷新到主存中,并强制对该变量的读操作从主存中读取最新的值,而不是使用缓存中的值。
作用
保证变量的可见性:
可见性指的是多个线程之间对共享变量的修改能否被及时地通知到其他线程,也就是说,当一个线程修改了共享变量的值时,其他线程能够立即看到这个变化。如果共享变量的可见性不能得到保证,就可能出现数据不一致的情况。
当一个变量被 volatile 修饰时,任何对该变量的写操作都会立即刷新到主存中,而任何对该变量的读操作都会从主存中读取最新的值。这样可以保证多个线程之间对该变量的读写操作都是可见的。
禁止指令重排:
指令重排指的是编译器或处理器为了提高程序运行效率而对指令序列进行重新排序的过程。在多线程环境下,指令重排可能会导致程序的执行结果与预期结果不一致。因此,为了保证程序的正确性,需要使用 volatile 关键字或 synchronized 关键字来禁止指令重排。具体来说,当一个变量被volatile修饰时,编译器和处理器会插入一些内存屏障,保证指令的执行顺序不会被打乱。
内存屏障是一种特殊的CPU指令,它可以保证在执行到内存屏障之前的所有指令都已经执行完成,并且其结果已经被写入内存中。在执行内存屏障之后的指令,必须等待前面的指令执行完成后,才能开始执行。这样可以避免由于 CPU 的乱序执行导致的指令重排和内存操作的乱序问题。synchronized 和 volatile 在实现上都使用了内存屏障来保证线程安全性。synchronized 使用了一种特殊的内存屏障——“内存锁定”,它可以保证线程在获取锁之前,所有对共享变量的修改操作都已经被刷新到主内存中,同时在释放锁之后,所有对共享变量的修改操作都已经对其他线程可见。而volatile使用了“写屏障”和“读屏障”,它们分别保证写操作和读操作在指令执行时都不会受到指令重排的影响,从而保证了线程对共享变量的访问操作的可见性和禁止指令重排。
编译器和处理器为了提高程序的执行效率,可能会对指令进行重排。使用volatile关键字可以禁止这种优化,保证指令的执行顺序不会被打乱。
不能保证原子性
原子性指的是一个操作是不可被中断的,要么全部执行,要么全部不执行。在多线程环境下,如果某个操作不是原子性的,就可能会出现多个线程同时修改同一个变量的情况,从而导致数据不一致。
如果需要保证原子性,可以使用 synchronized 关键字或者juc.atomic包中的原子类。
可见性、有序性、原子性
可见性、有序性、原子性三个特性都是线程安全的必要条件,三个特性缺一不可。因此,保证其中任何一个特性都不能认为是线程安全的全部,而只有同时保证三个特性才能实现真正的线程安全。
synchronized 关键字是一种保证线程安全的机制,它可以实现原子性和有序性,同时也可以保证可见性,因此使用 synchronized 可以保证线程安全,但是会影响程序的性能。synchronized 通过独占锁来保证同步代码块的原子性和有序性,同时在获取和释放锁的过程中,会使用内存屏障来保证可见性和有序性。
在Java 5之后,JDK 引入了 volatile 关键字,它可以保证可见性和有序性,但是无法保证原子性。volatile 关键字只能保证共享变量的读写操作是原子的,但是不能保证复合操作的原子性,如递增操作。因此,在需要保证原子性的情况下,仍然需要使用 synchronized 关键字。
所以,使用 synchronized 可以保证线程安全,并且同时保证了可见性、有序性和原子性,而使用 volatile 只能保证可见性和有序性,无法保证原子性。
不会导致线程阻塞
线程阻塞指的是线程暂停执行,等待某个条件得到满足后再继续执行的一种状态。线程阻塞可以分为两种情况:
主动阻塞:主动阻塞是指线程在执行过程中,调用了某个方法或者操作,而该方法或者操作需要等待某个条件的满足才能继续执行。例如,线程调用了sleep方法,就会主动阻塞一段时间;线程调用了wait方法,就会主动阻塞,等待其他线程的唤醒。被动阻塞:被动阻塞是指线程在执行过程中,由于某些原因(如I/O操作、锁竞争等)而无法继续执行,进入阻塞状态。例如,线程等待某个资源的释放,就会被动阻塞。
无论是主动阻塞还是被动阻塞,都会导致线程暂停执行,直到某个条件得到满足或者等待时间到期,才会重新被唤醒并继续执行。因此,在多线程编程中需要注意线程的阻塞状态,避免出现死锁、饥饿等问题。
volatile 关键字不会阻塞线程,它只是保证对被修饰的变量的读写操作都从主存中进行,而不是使用缓存中的值。
使用场景
1、多个线程之间共享一个变量,并且其中一个线程对该变量的修改需要立即对其他线程可见时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 将myVariable变量声明为volatile类型。
* 在incrementmyVariable()方法中,我们对myVariable的值进行了修改,而不是使用++运算符。
* 这是因为++运算符不是一个原子操作,它实际上包含读取变量值、增加变量值和写回变量值这三个步骤,
* 如果多个线程同时执行这些步骤,可能会导致值的不一致。
* 因此,使用了一个简单的加法操作来对myVariable进行修改,这样就可以确保线程安全。
* @author Administrator
*/
public class MyVolatile1 {
private volatile int myVariable = 0;
public void incrementMyVariable() {
myVariable = myVariable + 1;
}
public int getMyVariable() {
return myVariable;
}
}
2、多个线程之间共享一个变量,并且该变量的值可能会被多个线程同时修改时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 将myVariable变量声明为volatile类型。由于多个线程可能同时修改该变量的值,
* 需要使用synchronized关键字来保护它。
* incrementMyVariable()、decrementMyVariable()、getMyVariable()方法中,
* 都使用了synchronized关键字来确保对共享变量的访问是原子的。这样就可以确保线程安全。
*
*
*
* 为什么myVariable = myVariable + 1;是线程安全;myVariable ++不是?
* myVariable++操作实际上包含了三个步骤:
* 1、读取myVariable的值、
* 2、将其增加1、
* 3、将结果写回到myVariable。
* 如果有多个线程同时执行myVariable++操作,就会出现竞态条件,导致myVariable的值不确定。
*
* 例如,假设myVariable的初
多方面解读Java中的volatile关键字.rar
需积分: 5 19 浏览量
2023-07-10
22:21:10
上传
评论
收藏 5KB RAR 举报
小徐博客
- 粉丝: 1397
- 资源: 827
最新资源
- 第12章spring-mvc自定义类型转换器
- 基于PHP图书管理系统实验报告.docx
- Python爬取淘宝热卖商品并可视化分析
- 5152单片机proteus仿真和源码将按键次数写入AT24C02再读出并用1602LCD显示
- SE-SSD复现过程(Det3D的安装教程)
- 基于Python的在线学习与推荐系统设计与实现(论文+源码)-kaic
- 串口通过 YMODEM 协议进行文件传输
- 蓝桥杯2024年第十五届省赛真题-前缀总分
- com.qihoo.appstore_300101305-1.apk
- tensorflow-gpu-2.7.1-cp37-cp37m-manylinux2010-x86-64.whl
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈