volatile关键字修饰对象是什么效果?

如果volatile的修饰的是一个引用类型的对象变量,那么对象中定义的一些普通全局变量是否会受到volatile关键字的效果影响呢?”

接下来,我们就一起来分析下这个问题!让我们先通过一个例子来回顾下volatile关键字的作用!

public class VolatitleFoo {
    //类变量
    final static int max = 5;
    static int init_value = 0;

    public static void main(String args[]) {
        //启动一个线程,当发现local_value与init_value不同时,则输出init_value被修改的值
        new Thread(() -> {
            int localValue = init_value;
            while (localValue < max) {
                if (init_value != localValue) {
                    System.out.printf("The init_value is update ot [%d]\n", init_value);
                    //对localValue进行重新赋值
                    localValue = init_value;
                }
            }
        }, "Reader").start();
        //启动updater线程,主要用于对init_value的修改,当local_value=5的时候退出生命周期
        new Thread(() -> {
            int localValue = init_value;
            while (localValue < max) {
                //修改init_value
                System.out.printf("The init_value will be changed to [%d]\n", ++localValue);
                init_value = localValue;
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Updater").start();
    }
}

在上面的代码示例中,我们定义了两个类变量max、init_value,然后在主线程中分别启动一个Reader线程,一个Updater线程。Updater线程做的事情就是在值小于max的值时以每两毫秒的速度进行自增。而Reader线程则是在感知init_value值发生变化的情况下进行读取操作。

期望的效果是线程Updater更新init_value值之后,可以立刻被线程Reader感知到,从而进行输出显示。实际运行效果如下:

The init_value will be changed to [1]
The init_value will be changed to [2]
The init_value will be changed to [3]
The init_value will be changed to [4]
The init_value will be changed to [5]

实际的运行效果是在Updater修改类变量init_value后,Reader线程并没有立马感知到变化,所以没有进行相应的显示输出。而原因就在于共享类变量init_value在被线程Updater拷贝到该线程的工作内存中后,Updater对变量init_value的修改都是在工作内存中进行的,完成操作后没有立刻同步回主内存,所以Reader线程对其改变并不可见。

为了解决线程间对类变量init_value的可见性问题,我们将类变量init_value用volatile关键字进行下修饰,如下:

static volatile int init_value = 0;

然后我们再运行下代码,看看结果:

The init_value will be changed to [1]
The init_value is update ot [1]
The init_value will be changed to [2]
The init_value is update ot [2]
The init_value will be changed to [3]
The init_value is update ot [3]
The init_value will be changed to [4]
The init_value is update ot [4]
The init_value will be changed to [5]
The init_value is update ot [5]

此时线程Updater对类变量init_value的修改,立马就能被Reader线程感知到了,这就是volatile关键字的效果,可以让共享变量在线程间实现可见,原因就在于在JVM的语义层面要求被volatile修饰的共享变量,在工作内存中的修改要立刻同步回主内存,并且读取也需要每次都重新从主内存中刷新一份到工作内存中后才可以操作。

关于以上适用volatile关键字修饰基本类型的类变量、实例变量的场景,相信大家会比较好理解。接下来,我们来继续改造下代码:

public class VolatileEntity {
    //使用volatile修饰共享资源i
    //类变量
    final static int max = 5;
    int init_value = 0;
    public static int getMax() {
        return max;
    }
    public int getInit_value() {
        return init_value;
    }
    public void setInit_value(int init_value) {
        this.init_value = init_value;
    }
    private static class VolatileEntityHolder {
        private static VolatileEntity instance = new VolatileEntity();
    }
    public static VolatileEntity getInstance() {
        return VolatileEntityHolder.instance;
    }
}

我们将之前代码中的类变量init_value放到实体类VolatileEntity中,并将其设计为一个实例变量,为了便于理解,我们将实体类VolatileEntity设计为单例模式,确保两个线程操作的是同一个共享堆内存对象。如下:

public class VolatileEntityTest {

    //使用volatile修饰共享资源
    private static VolatileEntity volatileEntity = VolatileEntity.getInstance();

    private static final CountDownLatch latch = new CountDownLatch(10);

    public static void main(String args[]) throws InterruptedException {
        //启动一个线程,当发现local_value与init_value不同时,则输出init_value被修改的值
        new Thread(() -> {
            int localValue = volatileEntity.init_value;
            while (localValue < VolatileEntity.max) {
                if (volatileEntity.init_value != localValue) {
                    System.out.printf("The init_value is update ot [%d]\n", volatileEntity.init_value);
                    //对localValue进行重新赋值
                    localValue = volatileEntity.init_value;
                }
            }
        }, "Reader").start();

        //启动updater线程,主要用于对init_value的修改,当local_value=5的时候退出生命周期
        new Thread(() -> {
            int localValue = volatileEntity.init_value;
            while (localValue < VolatileEntity.max) {
                //修改init_value
                System.out.printf("The init_value will be changed to [%d]\n", ++localValue);
                volatileEntity.init_value = localValue;
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Updater").start();
    }
}

在上述代码中线程Updater和Reader此时操作的是类变量VolatileEntity对象中的普通实例变量init_value。在VolatileEntity对象没被volatile关键字修饰之前,我们看下运行效果:

The init_value will be changed to [1]
The init_value will be changed to [2]
The init_value will be changed to [3]
The init_value will be changed to [4]
The init_value will be changed to [5]

与未被volatile修饰的int类型的类变量效果一样,线程Updater对VolatileEntity对象中init_value变量的操作也不能立马被线程Reader可见。如果此时我们不VolatileEntity类中单独用volatile关键字修饰init_value变量,而是直接VolatileEntity对象用volatile关键字修饰,效果会如何呢?

private static volatile VolatileEntity volatileEntity = VolatileEntity.getInstance();

此时VolatileEntity对象的引用变量被volatile关键字修饰了,然而其中的普通实例变量init_value并没有直接被volatile关键字修饰,然后我们在运行下代码看看效果:

The init_value will be changed to [1]
The init_value is update ot [1]
The init_value will be changed to [2]
The init_value is update ot [2]
The init_value will be changed to [3]
The init_value is update ot [3]
The init_value will be changed to [4]
The init_value is update ot [4]
The init_value will be changed to [5]
The init_value is update ot [5]

从实际的运行效果上看,虽然我们没有直接用volatile关键字修饰对象中的类变量init_value,而是修改了对象的引用,但是我们看到对象中的普通实例变量仍然实行了线程间的可见性,也就是说间接也相当于被volatile关键字修饰了。所以,在这里问题也就基本上有了答案,那就是:“被volatile关键字修饰的对象作为类变量或实例变量时,其对象中携带的类变量和实例变量也相当于被volatile关键字修饰了”。

这个问题主要是考查大家对volatile关键字的理解是否深入,另外也是对Java数据存储结构的考查,虽然可能大家对volatile关键字的作用会有了解,但是如果突然被问到这样的问题,如果不加以思考,在面试中也是很容易被问懵的!

原文地址:https://blog.51cto.com/14570694/2465356

时间: 2024-10-10 01:29:45

volatile关键字修饰对象是什么效果?的相关文章

java并发系列(六)-----Java并发:volatile关键字解析

在 Java 并发编程中,要想使并发程序能够正确地执行,必须要保证三条原则,即:原子性.可见性和有序性.只要有一条原则没有被保证,就有可能会导致程序运行不正确.volatile关键字 被用来保证可见性,即保证共享变量的内存可见性以解决缓存一致性问题.一旦一个共享变量被 volatile关键字 修饰,那么就具备了两层语义:内存可见性和禁止进行指令重排序.在多线程环境下,volatile关键字 主要用于及时感知共享变量的修改,并使得其他线程可以立即得到变量的最新值,例如,用于 修饰状态标记量 和 D

volatile关键字作用

(1)保证可见性:多线程主要围绕可见性和原子性两个特性而展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,一定是最新的数据. 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值. 而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性. (2)防止指令重排:代码底层执

Java多线程——volatile关键字、发布和逸出

1.volatile关键字 Java语言提供了一种稍弱的同步机制,即volatile变量.被volatile关键字修饰的变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在每次读取volatitle类型变量时总会返回最新的值,而不是从寄存器中获取. 加锁机制既然可以确保可见性又可以确保原子性,而volatile只能确保可见性. 2.发布和逸出 "发布(Publish)"一个对象指,使对象能够在当前作用域之外的代码中使用.如将指向该对象的引用保存到其他代码可以访问的地方,或者在某一

volatile关键字与内存可见性

前言 首先,我们使用多线程的目的在于提高程序的效率,但是如果使用不当,不仅不能提高效率,反而会使程序的性能更低,因为多线程涉及到线程之间的调度.CPU上下文的切换以及包括线程的创建.销毁和同步等等,开销比单线程大,因此需谨慎使用多线程. 在jdk1.5以后,提供了一个强大的java.util.current包,这个包中提供了大量的应用于线程的工具类. 下面开始介绍volatile关键字和内存可见性,虽然volatile是在jdk1.5之前就有的,但还是想放在这里讲一下. 举例说明 首先,我们先看

C语言中的volatile关键字

说明: ******一个被volatile关键字修饰的变量可能会被意想不到的改变,本意就是"易变的". ******对于被volatile修饰的变量,当编译器在编译的时候不会去假设该变量的值 ******对于被volatile修饰的变量,当优化器优化时,必须每次都小心的重新读取该变量的值,而不能使用寄存器里的备份. 使用volatile关键字的几种情况: ******第一种情况:并行设备中的状态寄存器(当不同设备访问时,状态可能会随时变化的,所以要用volatile关键字修饰) ***

volatile关键字使用

1.volatile 使用场景(多线程情况下): 适合使用在 一写多读 的情况下: 2.volatile 理解分析: 使用 volatile 关键字修饰的变量,值在改变时会直接刷新到 主内存 中,而不是停留在 工作内存 中: 只有 volatile 修饰的变量并不能保证线程的安全性,修饰的变量不具有原子性(除非一些原子的操作:如 a = 100:):

Java中volatile关键字的作用

在Java内存模型中,有main memory(主内存)还每个线程各自的线程内存memory(例如:寄存器).为了性能一个线程会在自己memory中保持要访问变量的副本.这样就会出现同一个变量在某一个时刻一个线程内存中的值和其他线程内存或者主内存中的值不一致. 一个变量声明为volatile,就意味着这个变量随时会被其他线程修改,因此不能将他cahe在线程memory中,即:不会再memory中保留他的副本,下面看个例子: public class TestVolatile extends Th

Java多线程编程——volatile关键字

(本篇主要内容摘自<Java多线程编程核心技术>) volatile关键字的主要作用是保证线程之间变量的可见性. package com.func; public class RunThread extends Thread{ private boolean isRunning = true; // volatile private boolean isRunning = true; public boolean isRunning() { return isRunning; } public

单例模式中的volatile关键字

在之前学习了单例模式在多线程下的设计,疑惑为何要加volatile关键字.加与不加有什么区别呢?这里我们就来研究一下.单例模式的设计可以参考个人总结的这篇文章 ??背景:在早期的JVM中,synchronized存在巨大的性能开销.因此,有人想出了一个"聪明"的技巧:双重检查锁定(Double-Checked Locking).人们想通过双重检查锁定来降低同步的开销.下面是使用双重检查锁定来实现延迟初始化的示例代码. public class DoubleCheckedLocking