Java中volatile关键字的作用是什么举例详解

 更新时间:2025年04月22日 08:38:53   作者:+720  
这篇文章主要介绍了Java中volatile关键字的作用是什么的相关资料,volatile关键字在Java中用于修饰变量,提供可见性和禁止指令重排的特性,但不保证原子性,它通过内存屏障实现这些特性,文中通过代码介绍的非常详细,需要的朋友可以参考下

前言

volatile 是 Java 中的一个关键字,用于修饰变量。它提供了 可见性 和 禁止指令重排 的特性,但不保证 原子性。

1. 可见性 (Visibility)

  • 问题背景: 在多线程环境下,每个线程都有自己的工作内存(例如 CPU 缓存),线程对共享变量的读写操作可能不是直接在主内存中进行的,而是在工作内存中进行的。这可能导致一个线程修改了共享变量的值,而其他线程却看不到这个修改,从而读取到过期的值。
  • volatile 的作用: 当一个变量被 volatile 修饰时,它会保证:
    • 写操作: 当一个线程修改了 volatile 变量的值,这个新值会立即被 刷新 到主内存中。
    • 读操作: 当一个线程读取 volatile 变量时,它会从主内存中读取最新的值,而不是从自己的工作内存中读取。
  • 实现原理: volatile 通过 内存屏障 (Memory Barrier) 来实现可见性。内存屏障是一种 CPU 指令,它可以阻止编译器和 CPU 对指令进行重排序,并强制将工作内存中的数据刷新到主内存,或从主内存读取数据。

代码示例:

public class VolatileVisibilityExample {
    private static volatile boolean flag = false;
 // private static boolean flag = false; 如果不使用volatile,可能出现线程1无法停止的情况

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            while (!flag) {
                // ... (循环执行某些操作) ...
            }
            System.out.println("Thread 1 stopped.");
        });

        Thread thread2 = new Thread(() -> {
            try {
                Thread.sleep(1000); // 让 thread1 先执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            flag = true; // 修改 flag 的值
            System.out.println("Thread 2 set flag to true.");
        });

        thread1.start();
        thread2.start();
    }
}

2. 禁止指令重排 (Ordering)

  • 问题背景: 为了优化性能,编译器和 CPU 可能会对指令的执行顺序进行重排序(reordering),只要不影响单线程程序的执行结果。但在多线程环境下,指令重排可能导致程序出现意外的行为。
  • volatile 的作用: volatile 关键字可以禁止特定类型的指令重排:
    • 写后读: 不能将 volatile 变量的写操作重排到读操作之后。
    • 写后写: 不能将 volatile 变量的写操作重排到另一个 volatile 变量的写操作之后。
    • 读后读/写: 不能将volatile变量的读操作重排到另一个volatile变量的读操作或写操作之后。
  • 实现原理: volatile 通过插入特定类型的内存屏障来禁止指令重排。例如,在 volatile 变量的写操作之后会插入一个 StoreStore 屏障,在读操作之前会插入一个 LoadLoad 屏障,以及可能的StoreLoad屏障。
  • 双重检查锁定 (Double-Checked Locking) 的例子: volatile 经常用于双重检查锁定模式,以防止指令重排导致的问题。
    //单例模式
    public class Singleton{
        //使用volatile禁止instance = new Singleton()的指令重排
        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;
        }
    }
    

3. 不保证原子性 (Atomicity)

  • 重要说明: volatile 不保证 操作的原子性。原子性指的是一个操作是不可分割的,要么全部执行,要么不执行,不会出现执行一半的情况。
  • 例子: volatile 变量的自增操作 (i++) 不是原子性的。它实际上包含了三个步骤:读取 i 的值、将 i 的值加 1、将新值写回 i。在多线程环境下,这三个步骤之间可能会被其他线程打断,导致结果错误。
  • **解决:**如果需要保证原子性,可以使用 synchronizedReentrantLock 或原子类(如 AtomicInteger)。

总结:

  • volatile 关键字用于修饰变量,提供可见性和禁止指令重排的特性。
  • 可见性保证线程对 volatile 变量的读写操作都是直接在主内存中进行的。
  • 禁止指令重排防止编译器和 CPU 对 volatile 变量相关的指令进行重排序。
  • volatile 不保证 原子性。
  • volatile 通常用于状态标志、双重检查锁定等场景。

问题分析:

这个问题考察了对 volatile 关键字的理解,包括它的作用、特性(可见性、禁止指令重排)以及局限性(不保证原子性)。回答时需要清晰地解释这些概念,并提供一些代码示例来说明问题。

与其他问题的知识点联系:

  • 什么是 Java 内存模型(JMM)? volatile 是 Java 内存模型 (JMM) 的一部分,它定义了线程和主内存之间的交互规则。
  • 什么是 Java 中的原子性、可见性和有序性? volatile 提供了可见性和有序性(禁止指令重排),但不保证原子性。
  • 什么是 Java 的 happens-before 规则? volatile 变量的写操作 happens-before 于后续对该变量的读操作。
  • 什么是 Java 中的指令重排? volatile 可以禁止特定类型的指令重排。
  • Java 中的 synchronized 是怎么实现的? synchronized 提供了原子性、可见性和有序性。在某些情况下,volatile 可以作为 synchronized 的一种轻量级替代方案(如果只需要保证可见性和有序性)。
  • 你使用过 Java 中的哪些原子类? 原子类(如 AtomicInteger)提供了原子操作,可以用来替代 volatile + 额外同步的方案。
  • Java 中的 final 关键字是否能保证变量的可见性? final关键字可以保证可见性。

理解这些联系可以帮助你更全面地掌握 Java 并发编程的知识,并了解如何在实际应用中选择合适的同步机制。

到此这篇关于Java中volatile关键字的作用是什么的文章就介绍到这了,更多相关Java volatile关键字的作用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Maven模版Bug及解决办法

    Maven模版Bug及解决办法

    默认,会帮我们创建src/main/resources 按照Maven的规范,Maven会有3个目录,分别是: src/main/java : java源文件存放位置 src/main/resource : resource资源,如配置文件等 src/test/java : 测试代码源文件存放位置
    2016-04-04
  • minio安装部署及使用的详细过程

    minio安装部署及使用的详细过程

    MinIO是一个基于Apache License v2.0开源协议的对象存储服务,下面这篇文章主要给大家介绍了关于minio安装部署及使用的详细过程,文中通过实例代码以及图文介绍的非常详细,需要的朋友可以参考下
    2022-09-09
  • 利用5分钟快速搭建一个springboot项目的全过程

    利用5分钟快速搭建一个springboot项目的全过程

    Spring Boot的监控能够使开发者更好地掌控应用程序的运行状态,下面这篇文章主要给大家介绍了关于如何利用5分钟快速搭建一个springboot项目的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-05-05
  • eclipse输出Hello World的实现方法

    eclipse输出Hello World的实现方法

    这篇文章主要介绍了eclipse输出Hello World的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-11-11
  • java基本教程之多线程基本概念 java多线程教程

    java基本教程之多线程基本概念 java多线程教程

    多线程是Java中不可避免的一个重要主体。下面是对“JDK中新增JUC包”之前的Java多线程内容的讲解,JUC包是由Java大师Doug Lea完成并在JDK1.5版本添加到Java中的
    2014-01-01
  • Spring Boot 配置和使用多线程池的实现

    Spring Boot 配置和使用多线程池的实现

    这篇文章主要介绍了Spring Boot 配置和使用多线程池的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-06-06
  • 对spring task和线程池的深入研究

    对spring task和线程池的深入研究

    这篇文章主要介绍了对spring task和线程池的深入研究,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-12-12
  • javaCV开发详解之收流器实现

    javaCV开发详解之收流器实现

    这篇文章主要介绍了javaCV开发详解之收流器实现,对javaCV有研究的同学,可以参考下
    2021-04-04
  • SpringBoot使用Prometheus采集自定义指标数据的方法详解

    SpringBoot使用Prometheus采集自定义指标数据的方法详解

    随着微服务在生产环境大规模部署和应用,随之而来也带来了新的问题,其中比较关键的就是关于微服务的运维和监控,本文将结合微服务运维监控中的指标监控进行详细的说明,需要的朋友可以参考下
    2024-07-07
  • Java MyBatis实战之QueryWrapper中and和or拼接技巧大全

    Java MyBatis实战之QueryWrapper中and和or拼接技巧大全

    在Java中QueryWrapper是MyBatis-Plus框架中的一个查询构造器,它提供了丰富的查询方法,其中包括and和or方法,可以用于构建复杂的查询条件,这篇文章主要给大家介绍了关于Java MyBatis实战之QueryWrapper中and和or拼接技巧的相关资料,需要的朋友可以参考下
    2024-07-07

最新评论