Java volatile的几种使用场景分析

 更新时间:2024年03月27日 09:24:05   作者:大明哥_  
volatile 是一种轻量级的同步机制,它能保证共享变量的可见性,同时禁止重排序保证了操作的有序性,但是它无法保证原子性,本文给大家总结了Java olatile的使用场景有哪些,并通过代码示例讲解的非常详细,需要的朋友可以参考下

回答

volatile 是一种轻量级的同步机制,它能保证共享变量的可见性,同时禁止重排序保证了操作的有序性,但是它无法保证原子性。所以使用 volatile 必须要满足这两个条件:

  • 写入变量不依赖当前值。
  • 变量不参与与其他变量的不变性条件。

volatile 比较适合多个线程读,一个线程写的场合,典型的场景有如下几个:

  • 状态标志
  • 重检查锁定的单例模式
  • 开销较低的“读-写锁”策略

详解

volatile 使用条件

要想正确安全地使用 volatile ,必须要具备这两个条件:

  • 写入变量不依赖当前值:变量的新值不能依赖于之前的旧值。如果变量的当前值与新值之间存在依赖关系,那么仅使用 volatile 是不够的,因为它不能保证一系列操作的原子性。比如 i++。
  • 变量不参与与其他变量的不变性条件:如果一个变量是与其他变量共同参与不变性条件的一部分,那么简单地声明变量为 volatile 是不够的。

第一个条件很好理解,第二个条件这里需要解释下。

“变量不参与与其他变量的不变性条件”,这里的“不变性条件”指的是一个或多个变量在程序执行过程中需要保持的条件或关系,以确保程序的正确性。假设我们有两个变量,它们需要满足某种关系(例如,a + b = 99)。我们需要在多线程环境下保证这种关闭在任何时候都是成立的。如果这个时候我们只是将其中一个变量声明为 volatile,虽然确保了这个变量的更新对其他线程立即可见,但却不能保证这两个变量作为一个整体满足特定的不变性条件。在更新这两个变量的过程中,其他线程可能会看到这些变量处于不一致的状态。在这种情况下我们就需要使用锁或者其他同步机制来保证这种关系的整体一致性。

volatile 使用场景

volatile 比较适合多个线程读,一个线程写的场合

状态标志

当我们需要用一个变量来作为状态标志,控制线程的执行流程时,使用 volatile 可以确保当一个线程修改了这个标志时,其他线程能够立即看到最新的值。

public class TaskRunner implements Runnable {
    private volatile boolean running = true;  // 状态标志,控制任务是否继续执行

    public void run() {
        while (running) {  // 检查状态标志
            // 执行任务
            doSomething();
        }
    }

    public void stop() {
        running = false;  // 修改状态标志,使得线程能够停止执行
    }

    private void doSomething() {
        // 实际任务逻辑
    }
}

DCL 的单例模式

在实现单例模式时,为了保证线程安全,通常使用双重检查锁定(Double-Checked Locking)模式。在这种模式中,volatile 用于避免单例实例的初始化过程中的指令重排序,确保其他线程看到一个完全初始化的单例对象,具体来说,就是使用 volatile防止了Java 对象在实例化过程中的指令重排,确保在对象的构造函数执行完毕之前,不会将 instance 的内存分配操作指令重排到构造函数之外。

public class Singleton {
    // 使用 volatile 保证实例的可见性和有序性
    private static volatile Singleton instance;

    private Singleton() {
    }
    
    public static Singleton getInstance() {
        if (instance == null) {  // 第一次检查,避免不必要的同步
            synchronized (Singleton.class) {  // 锁定
                if (instance == null) {  // 第二次检查,确保只创建一次实例
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

开销较低的“读-写锁”策略

这种策略一般都是允许多个线程同时读取一个资源,但只允许一个线程写入的同步机制。这种“读-写锁”非常适合读多写少的场景,我们可以利用 volatile + 锁的机制减少公共代码路径的开销。如下:

public class VolatileTest {  
    private volatile int value;  
    
    //读,不加锁,提供效率 
    public int getValue() {   
        return value;   
    }   
    //写操作,使用锁,保证线程安全  
    public synchronized int increment() {  
        return value++;  
    }  
}

在 J.U.C 中,有一个采用“读-写锁”方式的类:ReentrantReadWriteLock,它包含两个锁:一个是读锁,另一个是写锁。

下面是伪代码:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class DataStructure {
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Object data = ...; // 被保护的数据

    public void read() {
        readWriteLock.readLock().lock(); // 获取读锁
        try {
            // 执行读操作
            // 例如,读取data的内容
        } finally {
            readWriteLock.readLock().unlock(); // 释放读锁
        }
    }

    public void write(Object newData) {
        readWriteLock.writeLock().lock(); // 获取写锁
        try {
            // 执行写操作
            // 例如,修改data的内容
        } finally {
            readWriteLock.writeLock().unlock(); // 释放写锁
        }
    }
}
  • 读操作 :多个线程可以同时持有读锁,因此多个线程可以同时执行 read() 方法。
  • 写操作: 只有一个线程可以持有写锁,并且在持有写锁时,其他线程不能读取或写入。

这种“读-写锁”策略提高了在多线程环境下对共享资源的读取效率,尤其是在读操作远远多于写操作的情况下。但是,它也会让我们的程序变更更加复杂,比如潜在的读写锁冲突、锁升级(从读锁升级到写锁)等问题。因此,在实际应用中,大明哥推荐直接使用 ReentrantReadWriteLock 即可,无需头铁自己造轮子。

以上就是Java volatile的几种使用场景分析的详细内容,更多关于Java volatile使用场景的资料请关注脚本之家其它相关文章!

相关文章

  • springboot前端传参date类型后台处理的方式

    springboot前端传参date类型后台处理的方式

    这篇文章主要介绍了springboot前端传参date类型后台处理的方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • 用Java验证pdf文件的电子章签名

    用Java验证pdf文件的电子章签名

    这篇文章主要介绍了如何用Java验证pdf文件的电子章签名,帮助大家更好的理解和使用Java,感兴趣的朋友可以了解下
    2020-12-12
  • java.util.Collections类—emptyList()方法的使用

    java.util.Collections类—emptyList()方法的使用

    这篇文章主要介绍了java.util.Collections类—emptyList()方法的使用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • 做java这么久了居然还不知道JSON的使用(一文带你了解)

    做java这么久了居然还不知道JSON的使用(一文带你了解)

    这篇文章主要介绍了做java这么久了居然还不知道JSON的使用(一文带你了解),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-07-07
  • springboot如何开启缓存@EnableCaching(使用redis)

    springboot如何开启缓存@EnableCaching(使用redis)

    在Spring Boot项目中集成Redis主要包括添加依赖到pom.xml、配置application.yml中的Redis连接参数、编写配置类、在启动类上添加@EnableCaching注解以及测试接口的查询和缓存验证等步骤,首先,需要在pom.xml中添加spring-boot-starter-data-redis依赖
    2024-11-11
  • 详解Java如何优雅的实现字典翻译

    详解Java如何优雅的实现字典翻译

    当我们在Java应用程序中需要对字典属性进行转换返回给前端时,如何简单、方便、并且优雅的处理是一个重要问题。在本文中,我们将介绍如何使用Java中的序列化机制来优雅地实现字典值的翻译,从而简化开发
    2023-04-04
  • 基于Java中UDP的广播形式(实例讲解)

    基于Java中UDP的广播形式(实例讲解)

    下面小编就为大家分享一篇基于Java中UDP的广播形式(实例讲解),具有很好的参考价值,希望对大家有所帮助
    2017-12-12
  • java数据结构与算法之希尔排序详解

    java数据结构与算法之希尔排序详解

    这篇文章主要介绍了java数据结构与算法之希尔排序,结合实例形式分析了希尔排序的概念、原理、实现方法与相关注意事项,需要的朋友可以参考下
    2017-05-05
  • IDEA中application.properties的图标显示不正常的问题及解决方法

    IDEA中application.properties的图标显示不正常的问题及解决方法

    这篇文章主要介绍了IDEA中application.properties的图标显示不正常的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • java.text.DecimalFormat类十进制格式化

    java.text.DecimalFormat类十进制格式化

    这篇文章主要为大家详细介绍了java.text.DecimalFormat类十进制格式化的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-03-03

最新评论