详解java中的并发关键字volatile

简介: 提高java的并发编程,就不得不提volatile关键字,不管是在面试还是实际开发中 volatile都是一个应该掌握的技能。他的重要性不言而喻。因此也有必要学好。

一、为什么要用到volatile关键字?


使用一个新技术的原因肯定是当前存在了很多问题,在Java多线程的开发中有三种特性:原子性、可见性和有序性。我们可以在这里简单的说一下:


1、原子性(Atomicity)


原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行,就好比你做一件事,要么不做,要么做完。java提供了很多机制来保证原子性。我们举一个例子,比如说常见的a++就不满足原子性。这个操作实际是a = a + 1;是可分割的。在运行的时候可能做了一半不做了。所以不满足原子性。

为了解决上面a++出现的问题,java提供了很多其他的关键字和类,比如说AtomicInteger、AtomicLong、AtomicReference等。


2、可见性(Visibility)


可见性就是指当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改。如果我们学过java内存模型的话,对下面这张图想必不陌生:

v2-f70a54bbe8b655383c163e7e9727d07f_1440w.jpg

每一个线程都有一份自己的本地内存,所有线程共用一份主内存。如果一个线程对主内存中的数据进行了修改,而此时另外一个线程不知道是否已经发生了修改,就说此时是不可见的。


这种不可见的状况会带来一个问题,两个线程有可能会操作同一份但是值不一样的数据。这时候怎么办呢?于是乎,今天的主角登场了,这就是volatile关键字。


volatile关键字的作用很简单,就是一个线程在对主内存的某一份数据进行更改时,改完之后会立刻刷新到主内存。并且会强制让缓存了该变量的线程中的数据清空,必须从主内存重新读取最新数据。这样一来就保证了可见性


3、有序性


程序执行的顺序按照代码的先后顺序执行就叫做有序性,但是有时候程序的执行并不会遵循,比如说下面的代码:

int i = 1;              
int j = 2;

这两行代码很简单,i=1,j=2,程序在运行的时候一定会先让i=1,然后j=2嘛?不一定,为什么会不一定,这是因为有可能会发生指令重排序,从名字看就知道,在运行的时候,代码会重新排列。这里面涉及到的就比较多了。我会在专门的文章中进行讲解。


为了防止上面的重排序,java依然提供了很多机制,比如volatile关键字等。这也是我们volatile关键字第二个使用的场景。


在上面我们从java并发编程的三个特征来分析了为什么会用到volatile关键字,主要是保证内存可见性和防止指令重排序。下面我们就来正式来分析一下这个volatile。


二、深入剖析


1、volatile保证原子性嘛?


在上面我们只说了volatile关键字会保证可见性和有序性,但是并没有说会不会保证原子性,原子性的概念我们已经说了,也就是一个操作,要么不执行,要么执行到底。我们可以使用代码来验证一下:

public class Test {
    private static volatile int a = 0;
    public static void main(String[] args) {
        Test test = new Test();
        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(() -> {
                try {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(++a);
                        Thread.sleep(500);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
            threads[i].start();
        }
    }
}

这段代码的含义是,有5个线程,每一个线程都对a进行递增。每个线程一次加10个数。按道理来讲,如果volatile关键字保证原子性的话,最后结果一定是50。我们可以运行一下看看结果:

v2-256d5bb3cf520b610bdd6e653e17af62_1440w.jpg

最后得出的结论就是volatile不保证原子性。既然不能保证原子性,那肯定就是非线程安全的。


2、单例模式的双重锁为什么要加volatile?


什么是双重锁的单例模式,我们给出代码可以看看。

public class Test2 {
    private static volatile Test2 test2;
    public static Test2 getInstance() {
        if(test2 == null) {
            synchronized (Test2.class) {
                if(test2 == null) {
                    test2 = new Test2();
                }
            }
        }
        return test2;
    }
}

这就是单例模式的双重锁实现,为什么这里要加volatile关键字呢?我们把test2 = new Test2()这行代码进行拆分。可以分解为3个步骤:


(1)memory=allocate();// 分配内存

(2)ctorInstanc(memory) //初始化对象

(3)test2=memory //设置s指向刚分配的地址


如果没有volatile关键字,可能会发生指令重排序。在编译器运行时,从1-2-3 排序为1-3-2。此时两个线程同时进来的时候出现可见性问题,也就是说一个线程执行了1-3,另外一个线程一进来直接返回还未执行2的null对象。而我们的volatile关键之前已经说过了,可以很好地防止指令重排序。也就不会出现这个问题了。


如果我们学过java并发系列的其他类比如说Atomic等,通过源码我们会发现volatile无处不在。

相关文章
|
4月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
87 2
|
4月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
72 1
|
4月前
|
存储 SQL 缓存
揭秘Java并发核心:深度剖析Java内存模型(JMM)与Volatile关键字的魔法底层,让你的多线程应用无懈可击
【8月更文挑战第4天】Java内存模型(JMM)是Java并发的核心,定义了多线程环境中变量的访问规则,确保原子性、可见性和有序性。JMM区分了主内存与工作内存,以提高性能但可能引入可见性问题。Volatile关键字确保变量的可见性和有序性,其作用于读写操作中插入内存屏障,避免缓存一致性问题。例如,在DCL单例模式中使用Volatile确保实例化过程的可见性。Volatile依赖内存屏障和缓存一致性协议,但不保证原子性,需与其他同步机制配合使用以构建安全的并发程序。
71 0
|
24天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
29天前
|
Java 数据库连接 数据库
如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面
本文介绍了如何构建高效稳定的Java数据库连接池,涵盖连接池配置、并发控制和异常处理等方面。通过合理配置初始连接数、最大连接数和空闲连接超时时间,确保系统性能和稳定性。文章还探讨了同步阻塞、异步回调和信号量等并发控制策略,并提供了异常处理的最佳实践。最后,给出了一个简单的连接池示例代码,并推荐使用成熟的连接池框架(如HikariCP、C3P0)以简化开发。
48 2
|
2月前
|
Java
【编程进阶知识】揭秘Java多线程:并发与顺序编程的奥秘
本文介绍了Java多线程编程的基础,通过对比顺序执行和并发执行的方式,展示了如何使用`run`方法和`start`方法来控制线程的执行模式。文章通过具体示例详细解析了两者的异同及应用场景,帮助读者更好地理解和运用多线程技术。
29 1
|
2月前
|
SQL 缓存 安全
[Java]volatile关键字
本文介绍了Java中volatile关键字的原理与应用,涵盖JMM规范、并发编程的三大特性(可见性、原子性、有序性),并通过示例详细解析了volatile如何实现可见性和有序性,以及如何结合synchronized、Lock和AtomicInteger确保原子性,最后讨论了volatile在单例模式中的经典应用。
42 0
|
3月前
|
Java API 容器
JAVA并发编程系列(10)Condition条件队列-并发协作者
本文通过一线大厂面试真题,模拟消费者-生产者的场景,通过简洁的代码演示,帮助读者快速理解并复用。文章还详细解释了Condition与Object.wait()、notify()的区别,并探讨了Condition的核心原理及其实现机制。
|
3月前
|
缓存 Java 编译器
JAVA并发编程volatile核心原理
volatile是轻量级的并发解决方案,volatile修饰的变量,在多线程并发读写场景下,可以保证变量的可见性和有序性,具体是如何实现可见性和有序性。以及volatile缺点是什么?
|
4月前
|
存储 Java
Java 中 ConcurrentHashMap 的并发级别
【8月更文挑战第22天】
56 5