迹忆客 专注技术分享

当前位置:主页 > 学无止境 > 编程语言 > Java >

Java 中的 Volatile 是如何工作的?

作者:迹忆客 最近更新:2023/02/03 浏览次数:

什么是Java中的 volatile 变量以及什么时候使用Java中的 volatile 变量是Java面试中著名的多线程面试题? 尽管许多程序员知道什么是 volatile 变量,但他们在第二部分失败了,即在 Java 中使用 volatile 变量,因为在 Java 中对 volatile 变量有清晰的理解和实践并不常见。

在本篇文章中,我们将通过提供 Java 中 volatile 变量的简单示例并讨论何时在 Java 中使用 volatile 变量来弥补这一差距。 无论如何,Java 中的 volatile 关键字用作指示 Java 编译器和 Thread 不缓存此变量的值并始终从主内存中读取它。

因此,如果大家想通过在 intboolean 变量中读取和写入等实现来共享读写操作是原子的任何变量,那么我们可以将它们声明为 volatile 变量。

从 Java 5 开始,随着自动装箱、枚举、泛型和变量参数等重大变化,Java 在 Java 内存模型 (JMM) 中引入了一些变化,这保证了从一个线程到另一个线程所做的更改的可见性,也作为“先于发生” 解决了发生在一个线程中的内存写入可以“泄漏”并被另一个线程看到的问题。

Java volatile 关键字不能与方法或类一起使用,它只能与变量一起使用。 Java volatile 关键字还保证可见性和顺序,在 Java 5 写入任何 volatile 变量之后发生在任何读入 volatile 变量之前。

顺便说一句,使用 volatile 关键字还可以防止编译器或 JVM 对代码进行重新排序或将它们从同步屏障中移开。


Java 中的 Volatile 变量示例

为了理解 java 中 volatile 关键字的例子,让我们回到 Java 中的单例模式,看看在单例中使用 Volatile 和 java 中没有 volatile 关键字的双重检查锁定。

/**
 * 用于演示在 Java 中何处使用 Volatile 关键字的 Java 程序。
 * 在此示例中,Singleton Instance 被声明为 volatile 变量,
 * 以确保每个线程都能看到 _instance 的更新值。
 * 
 * @author Jiyik
 */
public class Singleton{
private static volatile Singleton _instance; //volatile variable 

public static Singleton getInstance(){

   if(_instance == null){
            synchronized(Singleton.class){
              if(_instance == null)
              _instance = new Singleton();
            }

   }
   return _instance;

}

如果仔细查看代码,我们将能够弄清楚:

  1. 我们只创建一次实例
  2. 我们在第一个请求到来时懒惰地创建实例。

如果我们不让 _instance 变量为 volatile,那么正在创建 Singleton 实例的 Thread 将无法与其他线程通信,该实例已经创建,直到它从 Singleton 块中出来,所以如果 Thread A 正在创建 Singleton 实例并且 就在创建失去 CPU 之后,所有其他线程将无法将 _instance 的值视为不为空,并且它们会认为它仍然为空。

 

Java 中 volatile 变量
Java 中 volatile 变量

 

为什么? 因为读取线程没有进行任何锁定,并且在写入线程退出同步块之前,内存不会同步,并且 _instance 的值不会在主内存中更新。

使用 Java 中的 Volatile 关键字,这由 Java 自己处理,并且所有读取器线程都可以看到此类更新。 所以在总结中除了Java中的synchronized 关键字外,还使用了一个 volatile 关键字来实现线程间内存内容的通信。


让我们看一下 Java 中 volatile 关键字的另一个例子

大多数时候在编写游戏时我们使用变量 bExit 来检查用户是否按下了退出按钮,这个变量的值在事件线程中更新并在游戏线程中检查,所以如果我们不使用 带有此变量的 volatile 关键字,如果 Game Thread 尚未在 Java 中同步,它可能会错过来自事件处理程序线程的更新。

java中的 volatile 关键字保证了 volatile 变量的值总是从主存中读取,Java内存模型中的“happens-before”关系保证了内存中的内容会被传递给不同的线程。

private boolean bExit; 
while(!bExit) {
    checkUserPosition(); 
    updateUserPosition();
}

在此代码示例中,一个线程(游戏线程)可以缓存“bExit”的值,而不是每次都从主内存中获取它,如果在任何其他线程(事件处理程序线程)之间更改该值; 该线程将看不到它。 在 Java 中将布尔变量“bExit”设置为 volatile 可确保不会发生这种情况。


什么时候在 Java 中使用 Volatile 变量?

学习 volatile 关键字最重要的事情之一是了解何时在 Java 中使用 volatile 变量。 许多程序员知道什么是 volatile 变量以及它是如何工作的,但他们从未真正将 volatile 修饰符用于任何实际目的。 下面是几个示例来演示何时在 Java 中使用 volatile 关键字:

1. 如果你想原子地读写longdouble变量,你可以使用 Volatile 变量。 longdouble 都是 64 位数据类型,默认情况下 longdouble 的写入不依赖于原子和平台。

许多平台在 longdouble 变量 2 步骤中执行写入,在每个步骤中写入 32 位,因此线程可能会看到来自两个不同写入器的 32 位。 我们可以通过在 Java 中将 longdouble 变量设置为 volatile 来避免这个问题。

2. 在某些情况下,volatile 变量可以用作在 Java 中实现同步的替代方法,例如 Visibility。 使用 volatile 变量,可以保证一旦写入操作完成,所有读取线程都会看到 volatile 变量的更新值,如果没有 volatile 关键字,不同的读取线程可能会看到不同的值。

3. volatile 变量可用于通知编译器某个特定字段可能会被多个线程访问,这将阻止编译器进行任何重新排序或任何类型的优化,这在多线程环境中是不可取的。

如果没有 volatile 变量,编译器可以重新排序代码,自由缓存 volatile 变量的值,而不是总是从主内存中读取。 像下面没有 volatile 变量的例子可能会导致无限循环

private boolean isActive = thread; 
public void printMessage(){
    while(isActive){
        System.out.println("Thread is Active");
    }
}

如果没有 volatile 修饰符,则不能保证一个线程从其他线程看到 isActive 的更新值。 编译器也可以自由缓存 isActive 的值,而不是在每次迭代时从主内存中读取它。 通过使 isActive 成为 volatile 变量,我们可以避免这些问题。

4. 另一个可以使用 volatile 变量的地方是修复单例模式中的双重检查锁定。 正如我们在 Why should you use Enum as Singleton 中讨论的那样,双重检查锁定在 Java 1.4 环境中被破坏了?


Java 中 Volatile 关键字的要点

  1. Java中的 volatile 关键字是唯一对变量的应用,在类和方法中使用 volatile 关键字是非法的。
  2. Java中的 volatile 关键字保证 volatile 变量的值总是从主存中读取,而不是从Thread的本地缓存中读取。
  3. 在 Java 中,对于所有使用 Java volatile 关键字声明的变量(包括 longdouble 变量),读写都是原子的。
  4. 在 Java 中对变量使用 volatile 关键字可以降低内存一致性错误的风险,因为在 Java 中对 volatile 变量的任何写入都会与对该相同变量的后续读取建立先行关系。
  5. 从 Java 5 开始,对 volatile 变量的更改始终对其他线程可见。 更重要的是,这也意味着当线程读取 Java 中的 volatile 变量时,它不仅会看到对 volatile 变量的最新更改,还会看到导致更改的代码的副作用。
  6. 即使在 Java 中没有使用 volatile 关键字,对于大多数原始变量(除 longdouble 之外的所有类型),引用变量的读写都是原子的。
  7. 访问 Java 中的 volatile 变量永远不会有阻塞的机会,因为我们只是在进行简单的读取或写入,所以与同步块不同,我们永远不会持有任何锁或等待任何锁。
  8. 作为对象引用的 Java volatile 变量可能为 null
  9. Java volatile 关键字并不意味着原子,这是一个常见的误解,认为在声明 volatile ++ 将是原子的之后,要使操作成为原子,我们仍然需要使用 Java 中的同步方法或块来确保独占访问。
  10. 如果一个变量不在多个线程之间共享,则不需要对该变量使用 volatile 关键字。

Java 中 synchronized 和 volatile 关键字的区别

volatilesynchronized 之间的区别是多线程和并发面试中另一个流行的核心 Java 问题。 请记住,volatile 不是同步关键字的替代品,但在某些情况下可以用作替代品。

以下是 Java 中 volatilesynchronized 关键字之间的一些区别。

  1. Java中的 volatile 关键字是字段修饰符,synchronized 修饰的是代码块和方法。
  2. synchronized 获取和释放 monitor 的锁,Java的 volatile 关键字不需要。
  3. Java中的线程在同步的情况下可以阻塞等待任何监视器,而Java中的 volatile 关键字则不会。
  4. synchronized 方法比 Java 中的 volatile 关键字更能影响性能。
  5. 由于Java中的 volatile 关键字只同步线程内存和“主”内存之间的一个变量的值,而synchronized同步线程内存和“主”内存之间的所有变量的值并锁定和释放监视器以启动。 由于这个原因,Java 中的 synchronized 关键字可能比 volatile 有更多的开销。
  6. 我们不能在空对象上进行同步,但 Java 中的 volatile 变量可能为空。
  7. 从 Java 5 开始,写入 volatile 字段与监视器释放具有相同的记忆效应,从 volatile 字段读取与监视器获取具有相同的记忆效应

简而言之,Java 中的 volatile 关键字不是同步块或方法的替代品,但在某些情况下非常方便,并且可以节省 Java 中使用同步带来的性能开销。

转载请发邮件至 1244347461@qq.com 进行申请,经作者同意之后,转载请以链接形式注明出处

本文地址:

相关文章

C 中的 Volatile 限定符

发布时间:2023/05/07 浏览次数:150 分类:C语言

本文讨论 C 编程中的 volatile 限定符。 它还显示了何时以及如何在 C 程序中使用它。

在 Java 中获取文件大小

发布时间:2023/05/01 浏览次数:139 分类:Java

Java 提供了不同的方法来获取文件的字节大小。 本教程演示了在 Java 中获取文件大小的不同方法。使用 Java IO 的文件类获取文件大小 Java IO 包的 File 类提供了以字节为单位获取文件大小的功能。

Java 中的文件分隔符

发布时间:2023/05/01 浏览次数:108 分类:Java

本篇文章介绍了 Java 中的文件分隔符。Java 中的文件分隔符 文件分隔符是用来分隔目录的字符; 例如,Unix 使用 /,Windows 使用 \ 作为文件分隔符。

Java 中的文件过滤器

发布时间:2023/05/01 浏览次数:193 分类:Java

本篇文章介绍如何在 Java 中使用 FileFilter。FileFilter 用于过滤具有特定扩展名的文件。 Java内置包IO和Apache Commons IO为FileFilter提供了类和接口来进行文件过滤操作。

Java 获取 ISO 8601 格式的当前时间戳

发布时间:2023/05/01 浏览次数:132 分类:Java

本篇文章介绍了 ISO 8601 日期格式、其重要性及其在 Java 中的使用。 它还列出了一些优点来强调为什么应该使用 ISO 格式来表示日期。

在 Java 中获取数组的子集

发布时间:2023/05/01 浏览次数:142 分类:Java

本篇文章介绍了几种在 Java 中获取数组子集的方法。使用 Arrays.copyOf() 方法获取数组的子集 使用 Arrays.copyOfRange() 方法获取数组的子集

用 Java 填充二维数组

发布时间:2023/05/01 浏览次数:110 分类:Java

二维数组是基于表结构的,即行和列,填充二维数组不能通过简单的添加到数组操作来完成。 本篇文章介绍如何在 Java 中填充二维数组。

Java 中的自然排序

发布时间:2023/05/01 浏览次数:132 分类:Java

Java 中最常用的顺序是自然顺序。 本文将展示如何使用 naturalOrder() 函数对数组进行排序。

计算 Java 数组中的重复元素

发布时间:2023/05/01 浏览次数:202 分类:Java

本篇文章介绍Java计算数组中重复元素的方法。计算 Java 数组中的重复元素。我们可以创建一个程序来计算数组中的重复元素。 该数组可以是未排序的,也可以是已排序的。

扫一扫阅读全部技术教程

社交账号
  • https://www.github.com/onmpw
  • qq:1244347461

最新推荐

教程更新

热门标签

扫码一下
查看教程更方便