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关键字。

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