Java volatile关键字特性介绍

本文介绍了volatile关键字,其主要作用是使变量在多线程间可见,强制从公共堆栈取变量值。但它不支持原子性,无法保证线程安全。还将其与synchronized关键字对比,指出volatile是轻量级同步实现,性能好但功能少,synchronized功能全且能保证原子性和可见性。

一句话概括:

关键字volatile的主要作用是使变量在多个线程间可见。

 

补充介绍:

了解过JVM工作原理的都应该知道,在多线程环境中,每个私有线程会拷贝公共堆栈上需要用到的变量数据,然后读取变量是从私有数据栈中读取的,这样可能造成读取的数据不是实时的,出现脏读情况。而用volatile修饰的公共变量则每次都强制从公共堆栈中取得变量的值,而不是从私有数据栈中读取。这样至少能保证每次读取到的共享数据是实时的。

关键字volatile主要使用场合是在多个线程中可以感知实例变量被更改了,并且可以获得最新的调用,也就是用多个线程读取共享变量时可以获得最新值的使用。

 

不支持原子性:

volatile关键字增加了实例变量在各个线程之间的可见性,但它最致命的缺点就是不支持原子性。

关键字volatile提示线程每次从共享内存中读取变量而不是从私有内存中读取,这样就保证了同步数据的可见性。但是这里需要注意的是:如果修改实例变量中的数据,比如i++,也就是i=i+1, 则这样的操作其实并不是一个原子操作,也就是非线程安全的。表达式i++的操作步骤分解如下:

1)从内存中取出i的值

2)计算i的值

3)将i的值写到内存中。

假如在第2步计算值的时候,另外一个线程也修改了i的值,那么这个时候就会出现脏读数据。所以说volatile本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存。

从Java加载变量的8个阶段来看volatile出现非线程安全的原因:

1)read和load阶段:从主内存复制变量到当前线程工作内存;

2)use和assign阶段:执行代码,改变共享变量值;

3)store和write阶段:用工作内存数据刷新主内存对应的变量的值。

对于volatile修饰的变量,JVM只是保证从主内存加载到线程工作内存的值是最新的,例如线程1和线程2在进行read和load的操作中,都从主内存加载最新的count变量,待他们read和load之后,如果此主内存的count变量再次发生变化就不会影响到线程1和2了,因为他们已经加载完毕了,这个时候就造成了私有内存和公共内存中的数据不一致,那么相应的最后计算的结果肯定也不一致,也就是出现了线程安全问题。

所以说,volatile关键字解决的是变量读时的可见性问题,但无法保证原子性。如何保证数据原子性呢,还是需要加入synchronized关键字的帮助。因此我们在设计代码的时候,如果有需要考虑原子性的问题,还是尽量用synchronized关键字去做同步,如果不需要考虑原子性,那么volatile修饰变量时一种简单高效的办法。

 

volatile和synchronized关键字比较:

1)关键字volatile是线程同步的轻量级实现,所以性能上肯定比synchronized要好。不过随着JDK的更新,synchronized关键字的效率得到了很大的提升,所以一般性的用synchronized关键字还是比较多的,性能也非常好。

2)volatile只能修饰变量,而synchronized关键字可以修饰方法,代码块等,功能更全,当然操作也更麻烦。

3)多线程访问volatile不会发生阻塞,而synchronized会阻塞。

4)volatile能保证数据的可见性,但不能保证原子性。而synchronized关键字既可以保证可见性又能保证原子性,因为它会将私有内存和公共内存中的数据做同步。

5)关键字synchronized可以保证在同一时刻,只有一个线程可以执行某一个方法或者某一段代码块。它包含两个特性:互斥性和可见性,同步synchronized不仅可以解决一个线程看到对象处于不一致的状态,它还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护之前的所有最新修改效果。

 

 

 

 

 

 

<think>好的,用户想了解Javavolatile关键字的用法和作用,包括示例和使用场景。我需要根据提供的引用资料来整理回答。首先,volatile的作用主要有两点:可见性和禁止指令重排。需要详细解释这两个特性,并结合引用中的内容。 可见性方面,引用[1]提到volatile不会让线程阻塞,比synchronized轻量,但效果有限。引用[2]指出volatile保证变量的读写在主内存进行,确保多线程下的可见性。需要说明当线程写入volatile变量时,其他线程能立即看到新值。 禁止指令重排方面,引用[5]解释了volatile变量的操作不会被编译器或CPU重排序,这对单例模式的双重检查锁定很重要。引用[3]和[4]中的示例展示了双重检查锁中使用volatile来防止对象初始化时的指令重排,避免返回未完全初始化的实例。 使用场景需要包括状态标志和双重检查锁定。状态标志的例子可以是一个简单的布尔变量控制线程运行,而双重检查锁定则用于单例模式,确保线程安全且高效。引用[3]和[4]的代码示例需要解释,说明volatile如何防止问题。 同时,要指出volatile保证原子性,比如自增操作需要synchronized或Atomic类。引用[1]提到volatile的适用场景有限,所以需要强调适用的具体情况。 最后生成相关问题,可能涉及与synchronized的对比、不适用场景、底层原理等,结合已有引用内容中的知识点。确保回答结构清晰,分点说明,并正确使用LaTeX格式,如变量名用$volatile$等。检查引用标识是否正确对应,如[^1]、[^2]等。</think>### Java volatile关键字详解 #### 一、核心作用 1. **可见性保证** $volatile$ 修饰的变量修改后,会立即将新值同步到主内存,并使其他线程的缓存失效[^2]。例如: ```java volatile boolean flag = false; // 线程A修改后,线程B能立即读取到最新值 ``` 2. **禁止指令重排序** 编译器/CPU不会对$volatile$变量的读写操作进行重排序[^5]。在单例模式中可防止返回未完全初始化的对象: ```java instance = new Singleton(); // 实际包含三个步骤:分配内存、初始化对象、地址赋值 ``` #### 二、典型使用场景 1. **状态标志位** 适用于多线程间简单的状态控制: ```java volatile boolean shutdownRequested; public void shutdown() { shutdownRequested = true; } public void doWork() { while (!shutdownRequested) { /* 任务执行 */ } } ``` 2. **双重检查锁定(Double-Checked Locking)** 解决单例模式延迟初始化时的线程安全问题[^3][^4]: ```java class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if (instance == null) { // 第一次检查 synchronized (Singleton.class) { if (instance == null) { // 第二次检查 instance = new Singleton(); } } } return instance; } } ``` 若缺少$volatile$,可能返回**部分初始化**的对象(由于指令重排导致)[^4]。 #### 三、关键限制 1. **不保证原子性** $volatile$无法保证复合操作的原子性,如$i++$需要配合`synchronized`或`AtomicInteger`[^2]。 2. **性能考量** 每次读写都直接操作主内存,会降低局部性原理带来的性能优势[^1]。 #### 四、与synchronized对比 | 特性 | volatile | synchronized | |--------------------|-----------------------|-----------------------| | 上下文切换 | 无 | 有 | | 原子性 | 不保证 | 保证 | | 适用场景 | 单变量可见性/指令排序 | 代码块/方法级同步保护 | --- 相关问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值