Java关键字volatile详析

目录
  • 一、可见性
  • 二、关于指令重排

volatile关键字关于先说它的两个作用:

  • 保证变量在内存中对线程的可见性
  • 禁用指令重排

每个字都认识,凑在一起就麻了

这两个作用通常很不容易被我们Java开发人员正确、完整地理解,以至于许多同学不能正确地使用volatile

一、可见性

码:

public class VolatileTest {
    private static volatile int count = 0;

    private static void increase() {
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    increase();
                }
            }).start();
        }
        // 所有线程累加完成后输出
        while (Thread.activeCount() > 2) Thread.yield();
        System.out.println(count);
    }
}

代码很好理解,开了十个线程对同一个共享变量count做累加,每个线程累加1w次

count我们已经用volatile修饰,已经保证了count对十个线程在内存中的可见性,按理说十个线程执行完毕count的值应该10w

运行多次,结果都远小于期望值

是哪个环节出了问题?

你肯定听过一句话:volatile只保证可见性,不保证原子性

这句话就是答案,但是依旧很多人没搞懂其中的奥秘

说来话长我长话短说,简单来讲就是 count++这个操作不是原子的,它是分三步进行

  • 从内存读取 count 的值
  • 执行 count + 1
  • 将 count 的新值写回

要彻底搞懂这个问题,我们得从字节码入手

下面是increase方法编译后的字节码

看不懂没关系,我们一行一行来看:

  • GETSTATIC:读取 count 的当前值
  • ICONST_1:将常量 1 加载到栈顶
  • IADD:执行+1
  • PUTSTATIC:写入count最新值

ICONST_1IADD其实就是真正的++操作

关键点来了,volatile只能保证线程在GETSTATIC这一步拿到的值是最新的,但当该线程执行到下面几行指令时,这期间可能就有其它线程把count的值修改了,最终导致旧值把真正的新值覆盖

所以,并发编程中,只靠volatile修饰共享变量是不可靠的,最终还是要通过对关键方法加锁来保证线程安全

就如上面的demo,稍加修改就能实现真正的线程安全

最简单的,给increase方法加个synchronized (synchronized怎么实现线程安全的我就不啰嗦了,我以前讲过synchronized底层实现原理)

    private synchronized static void increase() {
        ++count;
    }

run几下:

这不就妥了嘛

到现在,对于以下两点你应该有了新的认知:

  • volatile保证变量在内存中对线程的可见性
  • volatile只保证可见性,不保证原子性

二、关于指令重排

并发编程中,cpu自身和虚拟机为了提高执行效率,都会采用指令重排(在保证不影响结果的前提下,将某些代码乱序执行)

  • 关于cpu:为了从分利用cpu,实际执行指令时会做优化;
  • 关于虚拟机:在HotSpot vm中,为了提升执行效率,JIT(即时编译)模式也会做指令优化

指令重排在大部分场景下确实能提升执行效率,但有些场景对代码执行顺序是强依赖的,此时我们需要禁用指令重排,如下面这个场景

伪代码取自《深入理解Java虚拟机》:

其描述的场景是开发中常见配置读取过程,只是我们在处理配置文件时一般不会出现并发,所以没有察觉这会有问题。
试想一下,如果定义initialized变量时没有使用volatile修饰,就可能会由于指令重排序的优化,导致位于线程A中最后一条代码“initialized=true”被提前执行(这里虽然使用Java作为伪代码,但所指的重排序优化是机器级的优化操作,提前执行是指这条语句对应的汇编代码被提前执行),这样在线程B中使用配置信息的代码就可能出现错误,而volatile通过禁止指令重排则可以避免此类情况发生

禁用指令重排只需要将变量声明为volatile,是不是很神奇

我们来看看volatile是如何实现禁用指令重排的:

这是个单例模式的实现,下面是它的部分字节码,红框中 mov%eax,0x150(%esi) 是对instance赋值

可以看到,在赋值后,还执行了 lock addl$0x0,(%esp) 指令,关键点就在这儿,这行指令相当于此处设置了个 内存屏障 ,有了内存屏障后,cpu或虚拟机在指令重排时就不能把内存屏障后面的指令提前到内存屏障前面,好好捋一下这段话

到此这篇关于Java关键字volatile详析的文章就介绍到这了,更多相关Java关键字volatile内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java并发编程之关键字volatile的深入解析

    目录 前言 一.可见性 二.有序性 总结 前言 volatile是研究Java并发编程绕不过去的一个关键字,先说结论: volatile的作用: 1.保证被修饰变量的可见性 2.保证程序一定程度上的有序性 3.不能保证原子性 下面,我们将从理论以及实际的案例来逐个解析上面的三个结论 一.可见性 什么是可见性? 举个例子,小明和小红去看电影,刚开始两个人都还没买电影票,小红就先去买了两张电影票,没有告诉小明.小明以为小红没买,所以也去买了两张电影票,因为他们只有两个人,所以他们只能用两张票,这就是

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

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

  • Java中JMM与volatile关键字的学习

    目录 JMM volatile关键字 可见性与原子性测试 哪些地方用到过volatile? 单例模式的安全问题 你知道CAS吗? CAS底层原理 CAS缺点 ABA问题 总结 JMM JMM是指Java内存模型,不是Java内存布局,不是所谓的栈.堆.方法区. 每个Java线程都有自己的工作内存.操作数据,首先从主内存中读,得到一份拷贝,操作完毕后再写回到主内存. JMM可能带来可见性.原子性和有序性问题. 1.可见性:指当一个线程修改了某一个共享变量的值,其他线程是否能够立即知道这个修改.显然

  • Java Volatile关键字你真的了解吗

    目录 正文 并发编程的三要素 1.原子性 2.可见性 3.有序性 Volatile Volatile 的内存模型 Volatile 的实现原理 (1) lock (2) unclock (3) read (4) load (5) use (6) assign (7) store (8) write Volatile源码案例 总结 正文 在谈 Volatile 之前,我们先回顾下 Java 内存模型 的三要素:原子性.可见性.有序性,也就是大家常提到的并发编程三要素. 并发编程的三要素 1.原子性

  • java 中的volatile关键字

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

  • Java关键字volatile详析

    目录 一.可见性 二.关于指令重排 volatile关键字关于先说它的两个作用: 保证变量在内存中对线程的可见性 禁用指令重排 每个字都认识,凑在一起就麻了 这两个作用通常很不容易被我们Java开发人员正确.完整地理解,以至于许多同学不能正确地使用volatile 一.可见性 码: public class VolatileTest {     private static volatile int count = 0;     private static void increase() {

  • Java那些鲜为人知的关键字volatile详析

    前言 在Java中,Java中volatile关键字十分重要 本文全面 & 详细解析volatile关键字,希望你们会喜欢 目录 1. 定义 Java 中的1个关键字 / 修饰符 2. 作用 保证 被 volatile修饰的共享变量 的可见性 & 有序性,但不保证原子性 3. 具体描述 下面,我将详细讲解 volatile是如何保证 "共享变量 的可见性 & 有序性,但不保证原子性"的具体原理 储备知识:原子性.可见性 & 有序性 3.1 保证可见性 具

  • Java中的关键字volatile详解

    volatile关键字经常用来修饰变量.不过,volatile本身很容易被误用.本篇就介绍一下volatile的原理和使用方式. 在介绍volatile关键字原理前,我们首先要了解JVM运行时的内存分配逻辑. 对于成员变量i,它存储在堆内存中.每个线程在运行时都会有一个自己的线程栈,线程如果要访问类的成员变量i,会通过引用获取到堆中变量i实际的值10,然后把这个变量值拷贝到自己的栈内存中,作为一个变量副本,之后线程便不再会与堆中的变量有实际联系.每个线程都有一个自己的本地副本,相互隔离.线程访问

  • java 关键字super详解及用法

    java 关键字super详解 一.super关键字 在JAVA类中使用super来引用父类的成分,用this来引用当前对象,如果一个类从另外一个类继承,我们new这个子类的实例对象的时候,这个子类对象里面会有一个父类对象.怎么去引用里面的父类对象呢?使用super来引用,this指的是当前对象的引用,super是当前对象里面的父对象的引用. 1.1.super关键字测试 package cn.galc.test; /** * 父类 * @author gacl * */ class Fathe

  • Java关键字volatile知识点总结

    volatile是什么 volatile关键字是Java提供的一种轻量级同步机制.它能够保证可见性和有序性,但是不能保证原子性 可见性 对于volatile的可见性,先看看这段代码的执行 flag默认为true 创建一个线程A去判断flag是否为true,如果为true循环执行i++操作两秒后,创建另一个线程B将flag修改为false 线程A没有感知到flag已经被修改成false了,不能跳出循环 这相当于啥呢?相当于你的女神和你说,你好好努力,年薪百万了就嫁给你,你听了之后,努力赚钱.3年之

  • Java 关键字 volatile 的理解与正确使用

    概述 Java语言中关键字 volatile 被称作轻量级的 synchronized,与synchronized相比,volatile编码相对简单且运行的时的开销较少,但能够正确合理的应用好 volatile 并不是那么的容易,因为它比使用锁更容易出错,接下来本文主要介绍 volatile 的使用准则,以及使用过程中需注意的地方. 为何使用volatile? (1)简易性:在某些需要同步的场景下使用volatile变量要比使用锁更加简单 (2)性能:在某些情况下使用volatile同步机制的性

  • Java关键字volatile和synchronized作用和区别

    volatile是变量修饰符,而synchronized则是作用于一段代码或方法:如下三句get代码: int i1; int geti1() {return i1;} volatile int i2; int geti2() {return i2;} int i3; synchronized int geti3() {return i3;} geti1() 得到存储在当前线程中i1的数值.多个线程有多个i1变量拷贝,而且这些i1之间可以相互不同.换句话说,另一个线程可能已经改变了它线程内的i1

  • Java 关键字static详解及实例代码

    一. static代表着什么 在Java中并不存在全局变量的概念,但是我们可以通过static来实现一个"伪全局"的概念,在Java中static表示"全局"或者"静态"的意思,用来修饰成员变量和成员方法,当然也可以修饰代码块. Java把内存分为栈内存和堆内存,其中栈内存用来存放一些基本类型的变量.数组和对象的引用,堆内存主要存放一些对象.在JVM加载一个类的时候,若该类存在static修饰的成员变量和成员方法,则会为这些成员变量和成员方法在固

  • Java并发编程之关键字volatile知识总结

    一.作用 被 volatile 修饰的变量 1.保证了不同线程对该变量操作的内存可见性 2.禁止指令重排序 二.可见性 Java 内存模型(Java Memory Model) 是 Java 虚拟机定义的一种规范,即每个线程都有自己的工作空间,线程对变量的操作都在线程的工作内存中完成,再同步到主存中,这样可能会导致不同的线程对共享变量的操作,在各自线程工作空间内不一样的问题. 而用 volatile 修饰的变量,线程对该变量的修改,会立刻刷新到主存,其它线程读取该变量时,会重新去主存读取新值.

  • java 关键字static详细介绍及如何使用

    java 关键字static 详解 一. static代表着什么 在Java中并不存在全局变量的概念,但是我们可以通过static来实现一个"伪全局"的概念,在Java中static表示"全局"或者"静态"的意思,用来修饰成员变量和成员方法,当然也可以修饰代码块. Java把内存分为栈内存和堆内存,其中栈内存用来存放一些基本类型的变量.数组和对象的引用,堆内存主要存放一些对象.在JVM加载一个类的时候,若该类存在static修饰的成员变量和成员方

随机推荐