Java中volatile关键字

简介: Java中volatile关键字

Java中volatile关键字

volatile的用法

在java中,为了保证多线程并发中的原子性问题、可见性问题和有序性问题。java语言定义了很多相关的关键字,比如说synchronized、volatile、final等。本文主要说明的就是volatile关键字

volatile一般可以理解成“轻量级别的synchronized”,这是因为其有些功能和synchronized相似。但和synchronized不同的是,volatile只能修饰变量,不能修饰方法或代码块。

而它的用法也很简单, 只需要在一个可能被多线程同时调用的变量前使用volatile修饰即可。如下面使用双重锁校验的单例模式:

public class Singleton{
    private volatile Singleton single;
    private Singleton(){}
    public Singleton getSingleton(){
        if(single == null){
            synchronized(Singleton.class){
                if(single == null){
                    single = new Singleton();
                }
            }
        }
        return single;
    }
}

如上代码是一个volatile关键字的简单用法示例,至于为什么使用双重锁校验,和为什么使用了volatile的同时又使用synchronized等相关问题,会在其他文章中解释,这里不多做赘述。

volatile的原理

说到原理,可能要稍微说一下计算机内存和缓存的相关知识。

大家应该知道,计算机主要的运算处理等都是在CPU中进行,而数据是存储在内存中。这里我们将计算机的物理内存称为主内存。CPU在处理数据的时候要和内存交互,去读取内存。
但随着技术的发展,CPU的处理速度一直在增加,导致去内存中读写的速度跟不上CPU的处理速度了。这时候,缓存技术就应运而生。

缓存就是保存一份主存中部分数据的备份,这部分数据正式CPU所要处理的。缓存的特点是速度快、内存小和比较贵。有了缓存,CPU处理数据就只需要去和缓存交互,当处理完了,
缓存会把数据在刷写入主存当中。就相当与在主内存和CPU中间有增加了一个高速的中介,那就是缓存。

但随着技术的进步,一级的缓存速度也不够了,这时候就出来的二级缓存,甚至是三级缓存。总之,道理大概都一样,速度更快,内存更小,价格更贵。每一级缓存都是存储了上一级的部分数据。

好的,由于缓存的出现,也带来了一些问题。因为,最开始的时候,只有主内存的时候,比如说现在有多个线程,每个线程访问的数据都是在主存中。这不会有什么问题,因为每个线程访问的都是同一个
变量。这个变量对所有线程都是可见的。比如说,一个变量初始值为1,线程1进行操作将其改为2了,这时候线程2是知道这个值变为2了的。但现在引入了缓存技术,缓存是这样的,单核的CPU问题还好,
因为单核的只有一套缓存,所有线程共用这套缓存,那数据的可见性也是可以保证的。现在问题是多核CPU下的多线程,因为在多核下的缓存是这样的:每个核有自己的一级缓存或者一级和二级,然后共享
三级缓存和内存。打个比方,现在比如说有多核CPU比较高级,拥有3级缓存。那现在是这样的,每个核有自己的一级和二级缓存。三级缓存是共享的,而三级缓存和主存交互。如下图:

Doc1.png

这时候,如果其中一个核上的线程更改了数值,这时候另外的核上缓存是不知道的(不可见)。比如,在主存中有变量a = 1;这时候core1中执行了a += 1;而core2中执行的是a += 2.
比如core1中先执行,因为处理过程两个核有自己的一级和二级缓存,这时候其实a已经等于2了,而core2中的L1保存的a还是等于1,所以他执行后会将a置为3,而实际我们想要的结果应该是a值为4。
这个就是缓存一致性问题。

而除了上面说的缓存一致性问题,在大多数处理器中,还存在乱序执行的问题,就是处理器为了更充分的使用资源,会进行处理器优化。而很多语言的编译器也会有类似的有话,比如说java虚拟机的
JIT(即时编译器)就会做指令重排操作。

看了这些物理层面上的理论,都有点儿忘了自己要说什么了。好了,现在让我们将其与今天的问题结合起来。其实在并发变成的过程中,我们关注的的是变量的可见性问题、原子性问题和有序性问题
而上面是说的缓存一致性问题所对应的就是可见性问题,而处理器优化乱序执行可能会导致原子性问题指令重排导致的就是有序性问题

现在铺垫的差不多了(其实还应该说一下java的内存模型,有兴趣的可以自行查阅资料),我们来说一下volatile的执行原理。

当使用volatile关键字修饰变量,当修改变量时,java虚拟机会向处理器发送一个lock前缀的指令,会让这个存于缓存中的值,马上回写到主存中。因为java的内存模型规定,每个线程只操作自己的工作内存,
工作内存之间是不可见的,变量的交互只能通过主存来传递。这里边有一个缓存一致性协议

说白了就是,当使用volatile关键字修饰变量时,变量修改后会马上刷写到主存中,而要使用该变量时,必须先重新从主存中读取该变量到缓存中。这样就解决了缓存一致性问题。

volatile_可见性问题

上面说了,volatile的机制,它可以解决缓存一致性问题,也就是它是能保证线程执行过程中,变量是可见的。

volatile可以解决可见性问题

volatile_有序性问题

前面我们提到,JIT进行指令重排时,可能会导致代码的执行顺序出现问题。比如,要执行的顺序是creat->search->delete,而由于指令重排,导致真正的执行顺序为search->delete->creat
这肯定是我们不能接受的。而volatile关键字可以有效的避免这个问题,因为它是可以禁止指令重排的。

volatile可以解决有序性问题

volatile_原子性问题

所谓原子性,简单来说就是一个操作是不可中断的,要不执行完毕,要不不执行。java中的synchronized中是使用字节码指令monitorentermonitorexit
来保证原子性的。而volatile关键字与这两个指令没有任何关系。所以其无法解决原子性问题。

volatile无法保证原子性

总结

本文主要就是针对volatile关键字做了简单的说明。

  1. volatile的运行原理。
  2. volatile与可见性、有序性和原子性问题的相关知识。
  3. 简单描述了计算机内存和缓存。

之后会再出文描述synchronized关键字。

相关文章
|
13天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
13天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
13天前
|
缓存 安全 Java
Java volatile关键字:你真的懂了吗?
`volatile` 是 Java 中的轻量级同步机制,主要用于保证多线程环境下共享变量的可见性和防止指令重排。它确保一个线程对 `volatile` 变量的修改能立即被其他线程看到,但不能保证原子性。典型应用场景包括状态标记、双重检查锁定和安全发布对象等。`volatile` 适用于布尔型、字节型等简单类型及引用类型,不适用于 `long` 和 `double` 类型。与 `synchronized` 不同,`volatile` 不提供互斥性,因此在需要互斥的场景下不能替代 `synchronized`。
2105 3
|
2月前
|
JavaScript 前端开发 Java
java中的this关键字
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。自学前端2年半,正向全栈进发。若我的文章对你有帮助,欢迎关注,持续更新中!🎉🎉🎉
56 9
|
2月前
|
设计模式 JavaScript 前端开发
java中的static关键字
欢迎来到瑞雨溪的博客,博主是一名热爱JavaScript和Vue的大一学生,致力于全栈开发。如果你从我的文章中受益,欢迎关注我,将持续分享更多优质内容。你的支持是我前进的动力!🎉🎉🎉
56 8
|
2月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
49 4
|
3月前
|
Java 程序员
在Java编程中,关键字不仅是简单的词汇,更是赋予代码强大功能的“魔法咒语”。
【10月更文挑战第13天】在Java编程中,关键字不仅是简单的词汇,更是赋予代码强大功能的“魔法咒语”。本文介绍了Java关键字的基本概念及其重要性,并通过定义类和对象、控制流程、访问修饰符等示例,展示了关键字的实际应用。掌握这些关键字,是成为优秀Java程序员的基础。
39 3
|
3月前
|
算法 Java
在Java编程中,关键字和保留字是基础且重要的组成部分,正确理解和使用它们
【10月更文挑战第13天】在Java编程中,关键字和保留字是基础且重要的组成部分。正确理解和使用它们,如class、int、for、while等,不仅能够避免语法错误,还能提升代码的可读性和执行效率。本指南将通过解答常见问题,帮助你掌握Java关键字的正确使用方法,以及如何避免误用保留字,使你的代码更加高效流畅。
46 3
|
3月前
|
存储 安全 Java
了解final关键字在Java并发编程领域的作用吗?
在Java并发编程中,`final`关键字不仅用于修饰变量、方法和类,还在多线程环境中确保对象状态的可见性和不变性。本文深入探讨了`final`关键字的作用,特别是其在final域重排序规则中的应用,以及如何防止对象的“部分创建”问题,确保线程安全。通过具体示例,文章详细解析了final域的写入和读取操作的重排序规则,以及这些规则在不同处理器上的实现差异。
了解final关键字在Java并发编程领域的作用吗?
|
3月前
|
Java 编译器
在Java中,关于final、static关键字与方法的重写和继承【易错点】
在Java中,关于final、static关键字与方法的重写和继承【易错点】
37 5