`

Java之 volatile 关键字原理详解

阅读更多
一、什么是 volatile ?

为了更好地了解Java中的volatile关键字,您将必须对Java内存模型中的变量发生的优化有所了解。假设您在代码中声明了一个名为 test 的变量。您会认为 test 变量将仅存储在RAM中,并且所有线程都将从那里读取测试变量的值。但是,为了使处理更快,处理器会将变量的值保存在其缓存中。在那种情况下,仅当高速缓存和内存之间发生同步时,对值的任何更改才会写回到主内存。

这将在多个线程正在读取或写入共享变量的地方引起问题。如果我们以在多个线程中使用的 test 变量为例,则可能出现以下情况:一个线程对仍存储在高速缓存中的 test 变量进行了更改,而另一个线程试图从主内存中读取测试变量的值 。这将导致内存和高速缓存数据不一致错误,因为不同的线程将读取/写入不同的测试变量值。



二、如何声明 volatile ?

将变量声明为 volatile 可确保始终从主存储器读取变量的值。 因此,在Java中将字段声明为volatile 可以提供线程可见性。从而确保每次对volatile字段进行都写操作,都会早于随后读取该字段的操作,即:(当前线程的)写操作对随后的(其它线程的)读操作都是可见的。

我们在上面看到的问题是因为 volatile 字段不会发生CPU缓存的值,因为可以保证线程1对volatile变量所做的更新始终对线程2可见。


三、 volatile 使用示例

Java中volatile关键字最常见的用法之一是声明为volatile的布尔状态标志,该标志指示事件的完成,以便另一个线程可以启动。

首先让我们看看如果在这种情况下不使用volatile会发生什么。

public class VolatileDemo {
    private static  boolean flag = false;
    public static void main(String[] args) {
        // Thread-1
        new Thread(new Runnable(){
            @Override
            public void run() {
                for (int i = 1; i <= 2000; i++){
                    System.out.println("value - " + i);
                }
                // changing status flag
                flag = true;
                System.out.println("status flag changed " + flag );
            }
            
        }).start();
        // Thread-2
        new Thread(new Runnable(){
            
            @Override
            public void run() {
                int i = 1;
                while (!flag){
                    i++;
                }
                System.out.println("Start other processing " + i);    
            }
            
        }).start();
    }
}


OUTPUT
....
....
value - 1997
value - 1998
value - 1999
value - 2000
status flag changed true


运行此代码后,您会看到第一个线程显示i直到2000的值,然后并更改状态标志。但是第二个线程不会打印消息“Start other processing ”,并且程序不会终止。 由于在while循环中的线程2中经常访问flag变量,因此编译器可以通过将flag的值放在缓存寄存器中来进行优化,然后它将继续测试循环条件(while(!flag)),而无需向主存储器中读取其中的值。

现在,如果您更改布尔变量标志并将其标记为volatile,这将确保一个线程对共享变量所做的更改对其他线程可见。

private static volatile boolean flag = false;


OUTPUT:结果正确
....
....
value - 1997
value - 1998
value - 1999
value - 2000
status flag changed true
Start other processing 68925258


四、 volatile 受变量声明的顺序的影响

当线程读取一个volatile变量时,它不仅会看到对volatile的最新更改,还会看到导致更改的代码的副作用。 这也称为(happens-before-extended-guarantee),由Java 5中的volatile 关键字提供。

例如,如果线程T1在更新volatile变量之前更改了其他变量,则线程T2也将获得那些在线程T1中更新volatile变量之前已更改的变量的更新变量。

这将我们带到可能在编译时发生的重新排序以优化代码的地步。 只要不改变语义,就可以对代码语句进行重新排序。

private int var1;
private int var2;
private volatile int var3;
public void calcValues(int var1, int var2, int var3){
    this.var1 = 1;
    this.var2 = 2;
    this.var3 = 3;
}


由于var3是 volatile 的,由于 happens-before extended guarantee,因此var1和var2的更新值也将被写入主内存,并且对其他线程可见。

如果将这些语句重新排序以进行优化怎么办。


this.var3 = 3;
this.var1 = 1;
this.var2 = 2;


现在,变量var1和var2的值在 volatile 变量var3更新后更新。 因此,这些变量var1和var2的更新值可能不可用于其他线程。

这就是为什么如果在更新其他变量之后对 volatile 变量进行读取或写入,则不允许重新排序的原因。


五、 volatile 只能保证 可见性,不能保证原子性

在只有一个线程正在写入变量而其他线程仅在读取的情况下(如在状态标志的情况下),volatile有助于正确查看变量值。 但是,如果许多线程正在读取和写入共享变量的值,那么volatile是不够的。 在那种情况下,由于竞争条件,线程可能仍会得到错误的值。

让我们用一个Java示例来说清楚,其中有一个SharedData类,其对象在线程之间共享。 在SharedData类中,计数器变量被标记为volatile。 创建了四个线程,它们使计数器递增,然后显示更新的值。 由于存在竞争条件,线程可能仍会获得错误的值。 请注意,您也可以在几次运行中获得正确的值。

public class VolatileDemo implements Runnable {
    SharedData obj = new SharedData();
    public static void main(String[] args) {
        VolatileDemo vd = new VolatileDemo();
        new Thread(vd).start();
        new Thread(vd).start();
        new Thread(vd).start();
        new Thread(vd).start();
    }
 
    @Override
    public void run() {
        obj.incrementCounter();
        System.out.println("Counter for Thread " + Thread.currentThread().getName() + 
                    " " + obj.getCounter());
    }    
}
 
class SharedData{
    public volatile int counter = 0;
    public int getCounter() {
        return counter;
    }
 
    public void incrementCounter() {
        ++counter;
    }
}



OUTPUT
Counter for Thread Thread-0 1
Counter for Thread Thread-3 4
Counter for Thread Thread-2 3
Counter for Thread Thread-1 3



六、 volatile 要点总结

在Java中volatile关键字只能与不带的方法和类变量使用。

标记为volatile的变量可确保不缓存该值,并且对volatile变量的更新始终在主内存中进行。

volatile 也保证了报表的重新排序不会发生这样的挥发性提供的之前发生延长的保证下volatile变量的更新之前更改其他变量也被写入主存储器和可见的其他线程。

volatile 确保仅可见性而不是原子性。

如果 final 变量也声明为 volatile,则是编译时错误。

使用 volatile 比使用 Lock 便宜。





























-
Refer: https://knpcode.com/java/multi-threading/volatile-keyword-in-java/
-
  • 大小: 6.6 KB
分享到:
评论

相关推荐

    Java中Volatile关键字详解及代码示例

    Java中Volatile关键字详解及代码示例 一、基本概念 在Java中,Volatile关键字是一个非常重要的概念,它与Java内存模型中的可见性、原子性和有序性息息相关。可见性是指线程之间的可见性,一个线程修改的状态对另一...

    Java并发教程之volatile关键字详解

    Java并发教程之volatile关键字详解 Java并发教程之volatile关键字的相关资料,对大家学习或者使用Java具有一定的参考学习价值。在Java中,volatile关键字是解决多线程问题的重要工具。本文将会详细介绍volatile...

    Java多线程 volatile关键字详解

    Java多线程volatile关键字详解主要介绍了Java多线程volatile关键字的应用和原理。volatile是一种轻量同步机制,可以确保变量的可见性和顺序性,但不保证原子性。 volatile关键字的作用 volatile关键字可以确保变量...

    Java中的关键字volatile详解

    一、volatile关键字原理 在Java中,volatile关键字经常用来修饰变量。volatile关键字的作用是使变量在多个线程之间可见。volatile关键字可以保证变量的可见性,但不能保证变量的原子性。 在Java虚拟机(JVM)中,...

    详解Java面试官最爱问的volatile关键字

    "Java面试官最爱问的volatile关键字详解" Java面试官最爱问的volatile关键字是Java并发编程中一个重要的概念,了解volatile关键字可以帮助开发者更好地理解Java内存模型(JMM)和Java并发编程的特性。本文将详细...

    violate java-Java 之 volatile 超级详解

    本文将深入探讨`volatile`关键字的工作原理、特性以及如何使用它来解决多线程中的可见性和有序性问题。 首先,我们了解`volatile`的基本含义。在Java中,当一个变量被声明为`volatile`时,编译器会确保对该变量的...

    java synchronized关键字原理、自定义一把锁来实现同步等

    ### Java synchronized 关键字原理与自定义锁实现详解 #### 一、Java synchronized 关键字原理 `synchronized` 是 Java 中的关键字之一,用于实现线程间的同步控制,确保共享资源的安全访问。它主要应用于以下两种...

    Java多线程synchronized关键字详解(六)共5

    在Java编程语言中,`synchronized`关键字是用于实现线程同步的重要机制,它确保了在多线程环境中的数据一致性与安全性。本篇将详细解析`synchronized`的关键特性和使用方法,帮助开发者深入理解如何在并发编程中有效...

    volatile使用详解

    ### Volatile 关键字详解 #### 一、Volatile 的定义与作用 `volatile` 是一种类型修饰符,用于声明的变量表示这类变量可能会被某些因素(如操作系统、硬件或其他线程)更改,这些更改是编译器所不知道的。在程序...

    java锁详解.pdf

    Java 锁详解 Java 锁是 Java 并发编程中的一种基本机制,用于确保线程安全和避免竞争条件。Java 锁可以分为两大类:synchronized 锁和 ReentrantLock 锁。 一、Synchronized 锁 1. 锁的原理:synchronized 锁是...

    java内存屏障与JVM并发详解实用.pdf

    在Java语言中,volatile关键字是一种特殊的关键字,用于标记变量的可见性。当一个变量被标记为volatile时,它的值将被强制刷新到主存储器中,从而确保其他线程可以看到最新的值。volatile关键字还可以强制处理器按照...

    Java 多线程synchronized关键字详解(六)

    8. **volatile关键字**: 虽然`synchronized`可以保证线程安全,但有时使用`volatile`关键字也能达到类似的效果,特别是在读多写少的场景下。`volatile`保证了变量的可见性和禁止指令重排序,但不提供互斥保证。 ...

    java线程详解

    Java线程:volatile关键字 Java线程:新特征-线程池 一、固定大小的线程池 二、单任务线程池 三、可变尺寸的线程池 四、延迟连接池 五、单任务延迟连接池 六、自定义线程池 Java线程:新特征-有返回值的线程...

    JAVA基础技术框架详解二.pdf

    * Java 并发编程的高级特性:包括 synchronized 关键字、volatile 关键字、CAS algorithm 等。 * Java 并发编程的实践:包括线程池、Executor 框架、Lock 框架等。 Java 网络编程 * Java 网络编程的基础:包括 ...

    04 并发编程专题04.zip

    "04 并发编程专题04.zip"这个压缩包文件包含了两个关于Java内存模型(JMM)和volatile关键字的深入讲解部分,分别是"JMM&volatile详解(上)(1).vep"和"JMM&volatile详解(上)(2).vep"。在这里,我们将详细探讨...

    Java CAS底层实现原理实例详解

    "Java CAS底层实现原理实例详解" Java CAS(Compare And Swap)是一种机制,用于解决多线程并行情况下使用锁造成性能损耗的问题。CAS 的概念是,比较并交换,解决多线程并行情况下使用锁造成性能损耗的一种机制。...

    各种java面试大全,包含nginx、mysql、redis、spring各种基础

    hashmap实现原理,java基础笔记,java基础面试全集,java里的volatile关键字详解,jvm垃圾回收,MySQL索引背后的数据结构及算法原理,MySQL性能优化的最佳21条经验,MySQL中的锁(表锁、行锁),Redis的优点和5种...

    java多线程设计模式详解(PDF及源码)

    在实际开发中,我们还需要关注线程安全问题,如volatile关键字确保变量在多线程环境中的可见性,Atomic类提供原子操作,避免并发修改引发的问题。此外,ThreadLocal为每个线程提供独立的变量副本,避免了线程间的...

    详解java如何正确使用volatile

    Java中的`volatile`关键字在多线程编程中扮演着至关重要的角色,它的主要功能是确保在并发环境中,被volatile修饰的变量对所有线程都具有实时可见性,并且禁止指令重排序。理解`volatile`的工作原理有助于我们更好地...

    java 多线程 同步详解

    Java多线程同步详解 在Java编程中,多线程是一种常见的并发执行方式,它可以提高程序的执行效率,充分利用CPU资源。然而,多线程环境下数据的安全性问题不容忽视,这就引出了Java中的同步机制。本文将深入探讨Java...

Global site tag (gtag.js) - Google Analytics