【Java基础】volatile关键字

简介: volatile 是一个Java关键字,可以用来修饰变量,volatile也被称为轻量级的synchronized,运行时开销比 synchronized更小。
关于作者:CSDN内容合伙人、技术专家, 从零开始做过日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。

一、导读

我们继续总结学习Java基础知识,温故知新。

二、概览

volatile 是一个Java关键字,可以用来修饰变量,volatile也被称为轻量级的synchronized,运行时开销比 synchronized更小。

2.1 作用

1、确保共享变量在线程之间的同步,实现可见性。
2、禁止处理器重排序。

2.2 多线程共享变量的访问流程

线程执行时,先拷贝主存数据到本线程本地,操作完成后再把结果从线程本地刷到主存。
我们看下面的图,多线程时,共享变量操作完的值是在红色的区域。
在这里插入图片描述

2.3 多线程为什么会出现可见性问题

可见性是由于CPU缓存引起,CPU 增加了缓存,以均衡与内存的速度差异,导致 可见性问题。
在多线程环境下,多个线程同时访问共享变量,由于不同线程的执行顺序和时间不确定,可能会导致一个线程对共享变量的修改在其他线程中不可见。可参考上面共享变量的访问流程。

2.4 volatile如何实现可见性

volatile不允许线程内进行缓存和重排序,直接修改内存,所以对其他线程是可见的。

被volatile修饰的变量读写时,都会直接刷到主存,从而使得变量可见。

2.5 如何实现禁止指令重排序

volatile是通过编译器在生成字节码时,在指令序列中添加“内存屏障”来禁止指令重排序的。

当一个变量被修饰时,表示变量是“易变的”(volatile)或者“不稳定的”(unstable),它意味着该变量的值可能会被其他线程(或进程)修改。

注意: volatile 关键字只能保证线程之间的同步,不能保证线程安全
要保证线程安全,需要使用其他同步机制,比如 synchronized 关键字或者 Lock 接口。

volatile具有可见性、有序性,但不具有原子性,所以是线程不安全的。

2.6 举例

  • 不使用volatile关键字
禁止线程缓存变量结果。
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。
引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。
举例:
// Thread-A
new Thread("Thread A") {
    @Override
    public void run() {
        while (!stop) {
        }
        System.out.println(Thread.currentThread() + " stopped");
    }
}.start();

一个线程内使用了停止的开关,假如这个stop没有被volatile修饰,我们在线程b中修改,
线程a并不知道开关的值被修改了。
  • 使用volatile 防重排序
从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现
public class Singleton {
public static volatile Singleton singleton;
    /**
    * 构造函数私有,禁止外部实例化
    */
    private Singleton() {};
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

实例化一个对象其实可以分为三个步骤:
* 分配内存空间。
* 初始化对象。
* 将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
* 分配内存空间。
* 将内存空间的地址赋值给对应的引用。
* 初始化对象

如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。
因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。
  • 使用volatile 保证原子性:单次读/写
对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,
但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作,要保证多步的原子性,
可以通过AtomicInteger或者Synchronized来实现,本质上就是cas操作。

2.7 来答题

  • 问题1: i++为什么不能保证原子性?
i++其实是一个复合操作,包括三步骤:
* 读取i的值。
* 对i加1。
* 将i的值写回内存。 
volatile是无法保证这三个操作是具有原子性的,
我们可以通过AtomicInteger或者Synchronized来保证+1操作的原子性。 
  • 问题2: 共享的long和double变量的为什么要用volatile?
因为long和double两种数据类型的操作可分为高32位和低32位两部分,
因此普通的long或double类型读/写可能不是原子的。
因此,鼓励大家将共享的long和double变量设置为volatile类型,
这样能保证任何情况下对long和double的单次读/写操作都具有原子性

三、原理

在JVM底层volatile是采用“内存屏障”来实现的,加入volatile关键字时,会多出一个lock前缀指令
内存屏障,又称内存栅栏,是一个 CPU 指令。

1、用javac命令进行编译生成.class文件,
2、再用javap命令反编译查看.class文件的信息,就可以看到字节码信息中多了一些指令。

为了保证各个处理器的缓存是一致的,实现了缓存一致性协议(MESI),每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。

volatile 变量通过这样的机制就使得每个线程都能获得该变量的最新值。

3.2 使用场景:

解决对变量可见性有要求,但是对读取顺序没要求的需求。

  • volatile特性:

(1)volatile仅能使用在变量级别
(2)volatile仅能实现变量的修改可见性,不能保证原子性,volatile + cas 就实现了原子性,如atomic包下面的类。
(3)volatile不会造成线程的阻塞
(4)volatile标记的变量不会被编译器优化

相关文章
|
3月前
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
70 0
|
23天前
|
SQL 缓存 安全
[Java]volatile关键字
本文介绍了Java中volatile关键字的原理与应用,涵盖JMM规范、并发编程的三大特性(可见性、原子性、有序性),并通过示例详细解析了volatile如何实现可见性和有序性,以及如何结合synchronized、Lock和AtomicInteger确保原子性,最后讨论了volatile在单例模式中的经典应用。
28 0
|
2月前
|
缓存 Java 编译器
JAVA并发编程volatile核心原理
volatile是轻量级的并发解决方案,volatile修饰的变量,在多线程并发读写场景下,可以保证变量的可见性和有序性,具体是如何实现可见性和有序性。以及volatile缺点是什么?
|
3月前
|
安全 Java 编译器
Java 中的 volatile 变量
【8月更文挑战第22天】
27 4
|
3月前
|
缓存 安全 Java
Java里为什么单利一定要加volatile呢?
【8月更文挑战第11天】Java里为什么单利一定要加volatile呢?
29 3
|
3月前
|
缓存 安全 Java
Java里volatile底层是如何实现的?
【8月更文挑战第11天】Java里的volatile底层是如何实现的?
26 2
|
4月前
|
存储 SQL Java
Java实现关键字模糊查询的高效方法及实践
实现关键字模糊查询的方法有多种,每种方法都有其适用场景。在选择合适的方法时,应考虑实际需求、数据量大小、性能要求等因素。正则表达式适用于处理简单文本或小数据集;数据库模糊查询适用于存储在RDBMS中的数据;而第三方库,则适合需要进行复杂搜索的大型项目。选用合适的工具,可以有效提升搜索功能的性能和用户体验。
95 6
|
3月前
|
安全 Java
|
4月前
|
算法 Java API
多线程线程池问题之synchronized关键字在Java中的使用方法和底层实现,如何解决
多线程线程池问题之synchronized关键字在Java中的使用方法和底层实现,如何解决
|
3月前
|
缓存 Java 编译器
Java中的volatile 变量是什么
【8月更文挑战第10天】Java中的volatile 变量是什么
47 0