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关键字的作用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java中利用反射调用另一类的private方法的简单实例

    java中利用反射调用另一类的private方法的简单实例

    下面小编就为大家带来一篇java中利用反射调用另一类的private方法的简单实例。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-06-06
  • 出现java.lang.NoSuchMethodException异常的解决(靠谱)

    出现java.lang.NoSuchMethodException异常的解决(靠谱)

    这篇文章主要介绍了出现java.lang.NoSuchMethodException异常的解决方案(靠谱),具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03
  • java 域对象共享数据的实现

    java 域对象共享数据的实现

    本文主要介绍了java 域对象共享数据的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-03-03
  • Java8 Comparator排序方法实例详解

    Java8 Comparator排序方法实例详解

    这篇文章主要介绍了Java8 Comparator排序方法实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Spring Boot2解决idea console 控制台输出乱码的问题

    Spring Boot2解决idea console 控制台输出乱码的问题

    这篇文章主要介绍了Spring Boot2解决idea console 控制台输出乱码的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • spring webflux响应式编程使用详解

    spring webflux响应式编程使用详解

    webflux,即响应式编程,响应式编程是一种用于处理异步数据流和事件的编程范式,spring webflux是spring在5.0版本后提供的一套响应式编程风格的web开发框架,本文给大家详细讲讲spring webflux响应式编程的使用,需要的朋友可以参考下
    2023-10-10
  • java中hashCode方法与equals方法的用法总结

    java中hashCode方法与equals方法的用法总结

    总的来说,Java中的集合(Collection)有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复
    2013-10-10
  • 如何使用java修改文件所有者及其权限

    如何使用java修改文件所有者及其权限

    这篇文章主要介绍了如何使用java修改文件所有者及其权限,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12
  • Java中的二维数组的赋值与输出方式

    Java中的二维数组的赋值与输出方式

    这篇文章主要介绍了Java中的二维数组的赋值与输出方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • 关于spring循环依赖问题及解决方案

    关于spring循环依赖问题及解决方案

    这篇文章主要介绍了关于spring循环依赖问题及解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06

最新评论