Java中Volatile关键字能保证原子性吗

目录
  • volatile
  • 有序性
  • 可见性
  • 是否能够保证原子性

volatile

volatile 是 Java 中的一个相对来说比较重要的关键字,主要就是用来修饰会被不同线程访问和修改的变量。

而这个变量只能保证两个特性,一个是保证有序性,另外一个则是保证可见性。

那么什么是有序性,什么又是可见性呢?

有序性

那么什么是有序性呢?

其实程序执行的顺序按照代码的先后顺序执行,禁止进行指令重排序。

看似理所当然,其实并不是这样,指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度。

但是在​​多线程​​环境下,有些代码的顺序改变,有可能引发逻辑上的不正确。

而 volatile 就是因为有这个特性,所以才被大家熟知的。

volatile 又是如何保证有序性的呢?

有很多小伙伴就说,网上说的是 volatile 可以禁止指令指令重排序,这就保证了代码的程序会严格按照代码的先后顺序执行。这就保证了有序性。被 volatile 修饰的变量的操作,会严格按照代码顺序执行,就是说当代码执行到 volatile 修饰的变量时,其前面的代码一定执行完毕,后面的代码一定没有执行。

如果这时候,面试官不再继续深挖下去的话,那么恭喜你,可能这个问题已经回答完了,但是如果面试官继续往下深挖,为什么会禁止指令重排,什么又是指令重排呢?

在从源码到指令的执行,一般是分成了三种重排,如图所示:

我们接下来就得看看 volatile 是如何禁止指令重排的。

我们直接用代码来进行验证:

public class ReSortDemo {

int a = 0;
boolean flag = false;

public void mehtod1(){
a = 1;
flag = true;
}

public void method2(){
if(flag){
a = a +1;
System.out.println("最后的值: "+a);
}
}
}

如果有人看到这段代码,肯定会说,那这段代码出来的结果会是什么呢?

有些人说是 2,是的, 如果你只是单线程调用,那结果就是 2,但是如果是多线程调用的时候,最后的输出结果不一定是我们想象到的 2,这时就要把两个变量都设置为 volatile。

如果大家对单例模式了解比较多的话,肯定也是关注过这个 volatile,为什么呢?

大家看看如下代码:

class Singleton {
// 不是一个原子性操作
//private static Singleton instance;
//改进,Volatile 可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!
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;
}
}

上面的单例模式大家熟悉么?

是的,这就是 **双重检查(DCL 懒汉式) **

有人会说,因为有指令重排序的存在,双端检索机制也也不一定是线程安全的呀,对呀,所以用到了 synchronized 关键字,让他变成了线程安全的了。

可见性

其实可见性就是,在多线程环境中,对共享变量的修改对于其他线程是否立即可见的问题。

那么他的可见性一般都会表现在什么地方呢?用在什么地方呢?

其实一般用这个变量,很多都是为了保证他的可见性,就比如定义的一个全局变量,在其中有个循环来判断这个变量的值,有一个线程修改了这个参数的时候,这个循环会停止,跳转到之后去执行。

我们来看看没有使用volatile修饰代码实现:

public class Test {

private static boolean flag = false;

public static void main(String[] args) throws Exception{
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程A开始执行:");
for (;;){
if (flag){
System.out.println("跳出循环");
break;
}
}
}
}).start();
Thread.sleep(100);

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程B开始执行");
flag = true;
System.out.println("标识已经变更");
}
}).start();
}
}

结果大家肯定是可想而知,

运行结果肯定是:

线程A开始执行:
线程B开始执行
标识已经变更

确实,就是这样的。

如果我们用 volatile 呢,那么这个代码的执行结果就会不一样呢?

我们来试一下:

public class Test {

private static volatile boolean flag = false;

public static void main(String[] args) throws Exception{
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程A开始执行:");
for (;;){
if (flag){
System.out.println("跳出循环");
break;
}
}
}
}).start();
Thread.sleep(100);

new Thread(new Runnable() {
@Override
public void run() {
System.out.println("线程B开始执行");
flag = true;
System.out.println("标识已经变更");
}
}).start();
}

这样我们就能看到另外一个执行结果,在循环当中的输出语句是可以被执行的。

也就是说,在线程B 中,我们去修改这个被修饰的变量,那么最终,在线程A中,就能顺利读取到我们的数据信息了。

是否能够保证原子性

不能,我们来看一点代码,被volatile修饰的变量,

public class Test {

// volatile不保证原子性
// 原子性:保证数据一致性、完整性
volatile int number = 0;

public void addPlusPlus() {
number++;
}

public static void main(String[] args) {
Test volatileAtomDemo = new Test();
for (int j = 0; j < 20; j++) {
new Thread(() -> {
for (int i = 0; i < 1000; i++) {
volatileAtomDemo.addPlusPlus();
}
}, String.valueOf(j)).start();
}// 后台默认两个线程:一个是main线程,一个是gc线程
while (Thread.activeCount() > 2) {
Thread.yield();
}
// 如果volatile保证原子性的话,最终的结果应该是20000 // 但是每次程序执行结果都不等于20000
System.out.println(Thread.currentThread().getName() +
" final number result = " + volatileAtomDemo.number);
}
}

如果能够保原子性,那么最终的结果应该是20000,但是每次的最终结果并不能保证就是20000,比如:

main final number result = 17114
main final number result = 20000
main final number result = 19317

三次执行,都是不同的结果,

为什么会出现这种呢?这就和number++有关系了

number++被拆分成3个指令

  • 执行GETFIELD拿到主内存中的原始值number
  • 执行IADD进行加1操作
  • 执行PUTFIELD把工作内存中的值写回主内存中

当多个线程并发执行PUTFIELD指令的时候,会出现写回主内存覆盖问题,所以才会导致最终结果不为 20000,所以 volatile 不能保证原子性。

到此这篇关于Java中Volatile关键字能保证原子性吗的文章就介绍到这了,更多相关Java Volatile关键内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 并发编程之Java内存模型volatile的内存语义

    1.volatile的特性 理解volatile特性的一个好办法是把对volatile变量的单个读/写,看成是使用同一个锁对单个读/写操作做了同步. 代码示例: package com.lizba.p1; /** * <p> * volatile示例 * </p> * * @Author: Liziba * @Date: 2021/6/9 21:34 */ public class VolatileFeatureExample { /** 使用volatile声明64位的long型

  • java并发编程关键字volatile保证可见性不保证原子性详解

    目录 关于可见性 关于指令重排 volatile关键字可以说是Java虚拟机提供的最轻量级的同步机制,但对于为什么它只能保证可见性,不保证原子性,它又是如何禁用指令重排的,还有很多同学没彻底理解 相信我,坚持看完这篇文章,你将牢牢掌握一个Java核心知识点 先说它的两个作用: 保证变量在内存中对线程的可见性禁用指令重排 每个字都认识,凑在一起就麻了 这两个作用通常很不容易被我们Java开发人员正确.完整地理解,以至于许多同学不能正确地使用volatile 关于可见性 不多bb,码来 public

  • Java并发编程之Volatile变量详解分析

    目录 一.volatile变量的特性 1.1.保证可见性,不保证原子性 1.2.禁止指令重排 二.内存屏障 三.happens-before Volatile关键字是Java提供的一种轻量级的同步机制.Java 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量, 相比synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度. 但是volatile 变量的同步性较差(有时它更简单并且开销更低),而且其

  • java 中的volatile关键字

    目录 1.volatile实现可见性的原理是什么? 2.演示volatile的可见性 1.volatile实现可见性的原理是什么? 有volatile变量修饰的共享变量进行写操作的时候汇编代码会多出一个Lock前缀指令. 在该指令下,多核处理器会引发两件事: 将当前处理器缓存行的数据写回系统内存 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效 这里需要简单了解CPU缓存一致性问题:多核处理器环境下,每个CPU都有自己的缓存行,缓存了内存中的数据,要维护多个CPU中缓存的数据一致性

  • 详解Java并发编程之volatile关键字

    目录 1.volatile是什么? 2.并发编程的三大特性 3.什么是指令重排序? 4.volatile有什么作用? 5.volatile可以保证原子性? 6.volatile 和 synchronized对比 总结 1.volatile是什么? 首先简单说一下,volatile是什么?volatile是Java中的一个关键字,也是一种同步机制.volatile为了保证变量的可见性,通过volatile修饰的变量具有共享性.修改了volatile修饰的变量,其它线程是可以读取到最新的值的 2.并

  • 为什么Java volatile++不是原子性的详解

    问题 在讨论原子性操作时,我们经常会听到一个说法:任意单个volatile变量的读写具有原子性,但是volatile++这种操作除外. 所以问题就是:为什么volatile++不是原子性的? 答案 因为它实际上是三个操作组成的一个符合操作. 首先获取volatile变量的值 将该变量的值加1 将该volatile变量的值写会到对应的主存地址 一个很简单的例子: 如果两个线程在volatile读阶段都拿到的是a=1,那么后续在线程对应的CPU核心上进行自增当然都得到的是a=2,最后两个写操作不管怎

  • Java中Volatile关键字能保证原子性吗

    目录 volatile 有序性 可见性 是否能够保证原子性 volatile volatile 是 Java 中的一个相对来说比较重要的关键字,主要就是用来修饰会被不同线程访问和修改的变量. 而这个变量只能保证两个特性,一个是保证有序性,另外一个则是保证可见性. 那么什么是有序性,什么又是可见性呢? 有序性 那么什么是有序性呢? 其实程序执行的顺序按照代码的先后顺序执行,禁止进行指令重排序. 看似理所当然,其实并不是这样,指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结

  • 谈谈Java中Volatile关键字的理解

    volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机.volatile关键字虽然从字面上理解起来比较简单,但是要用好不是一件容易的事情. 一.前言 JMM提供了volatile变量定义.final.synchronized块来保证可见性. 用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值.volatile很容

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

    一.基本概念 先补充一下概念:Java内存模型中的可见性.原子性和有序性. 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情.为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制. 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的.也就是一个线程修改的结果.另一个线程马上就能看到.比如:用volatile修饰的变量,就会具有可见性.volatile修饰的

  • 深入了解Java中Volatile关键字

    一.基本概念 先补充一下概念:Java 内存模型中的可见性.原子性和有序性. 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情.为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制. 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的.也就是一个线程修改的结果.另一个线程马上就能看到.比如:用volatile修饰的变量,就会具有可见性.volatile修饰

  • java中volatile关键字的作用与实例代码

    一,什么是volatile关键字,作用是什么 volatile是java虚拟机提供的轻量级同步机制 ​ 作用是: 1.保证可见性 2.禁止指令重排 3.不保证原子性 本篇具体就讲解 什么叫保证了可见性, 什么叫禁止指令重排,什么是原子性 而在这之前需要对JMM 有所了解 二,什么是JMM ​ JMM(java 内存模型 Java Memory Model 简称JMM) 本身是一个抽象的概念,并不在内存中真实存在的,它描述的是一组规范或者规则,通过这组规范定义了程序中各个变量(实例字段,静态字段和

  • Java中volatile关键字的作用与用法详解

    volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在Java 5之后,volatile关键字才得以重获生机. volatile 关键字作用是,使系统中所有线程对该关键字修饰的变量共享可见,可以禁止线程的工作内存对volatile修饰的变量进行缓存. volatile 2个使用场景: 1.可见性:Java提供了volatile关键字来保证可见性. 当一个共享变量被volatile修饰时,它会保证修

  • Java中volatile关键字的作用

    目录 一.volatile作用 二.什么是可见性 三.什么是总线锁和缓存锁 四.什么是指令重排序 一.volatile作用 可以保证多线程环境下共享变量的可见性 通过增加内存屏障防止多个指令之间的重排序 二.什么是可见性 可见性是指当一个线程对于共享变量的修改,其他线程可以立刻看到修改之后的一个值,可见性本质上由几个方面造成的 cpu层面的高速缓存,在cpu里面呢设计了三级缓存去解决cpu运算效率和内存IO效率的问题,但是它也带来缓存一致性的一个问题,而在多线程执行的情况下呢,缓存一致性的问题就

  • Java中volatile关键字实现原理

    前言 我们知道volatile关键字的作用是保证变量在多线程之间的可见性,它是java.util.concurrent包的核心,没有volatile就没有这么多的并发类给我们使用. 本文详细解读一下volatile关键字如何保证变量在多线程之间的可见性,在此之前,有必要讲解一下CPU缓存的相关知识,掌握这部分知识一定会让我们更好地理解volatile的原理,从而更好.更正确地地使用volatile关键字. CPU缓存 CPU缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为C

  • 深入解析Java中volatile关键字的作用

    在java线程并发处理中,有一个关键字volatile的使用目前存在很大的混淆,以为使用这个关键字,在进行多线程并发处理的时候就可以万事大吉. Java语言是支持多线程的,为了解决线程并发的问题,在语言内部引入了 同步块 和 volatile 关键字机制. synchronized 同步块大家都比较熟悉,通过 synchronized 关键字来实现,所有加上synchronized 和 块语句,在多线程访问的时候,同一时刻只能有一个线程能够用synchronized 修饰的方法 或者 代码块.

  • 解析java中volatile关键字

    在java多线程编程中经常volatile,有时候这个关键字和synchronized 或者lock经常有人混淆,具体解析如下: 在多线程的环境中会存在成员变量可见性问题: java的每个线程都存在一个线程栈的内存空间,该内存空间保存了该线程运行时的变量信息,当线程访问某一个变量值的时候首先会根据这个变量的地址找到对象的堆内存或者是栈堆存(原生数据类型)中的具体的内容,然后把这个内同赋值一个副本保存在本线程的线程栈中,紧接着对这个变量的一切操作在线程完成退出之前都和堆栈内存中的变量内容是没有关系

随机推荐