`
qindongliang1922
  • 浏览: 2146933 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
7265517b-f87e-3137-b62c-5c6e30e26109
证道Lucene4
浏览量:116311
097be4a0-491e-39c0-89ff-3456fadf8262
证道Hadoop
浏览量:124580
41c37529-f6d8-32e4-8563-3b42b2712a50
证道shell编程
浏览量:58448
43832365-bc15-3f5d-b3cd-c9161722a70c
ELK修真
浏览量:70345
社区版块
存档分类
最新评论

关于Java里面volatile关键字的重排序

    博客分类:
  • JAVA
阅读更多
Java里面volatile关键字主要有两个作用:

(1)可见性

(2)禁止指令重排序


第一条可见性比较容易理解,就是使用volatile修饰的共享变量,如果有一个线程修改了值,其他的线程里面是立即可见的。原理是对volatile变量的读写,都会强制线程操作从主内存。


第二条禁止指令重排序,能够保证局部的代码执行的顺序。假设我们现在有如下的一段代码:

```
     int a=2;
     int b=1;
```


从顺序上看a应该先执行,而b会后执行,但实际上却不一定是,因为cpu执行程序的时候,为了提高运算效率,所有的指令都是并发的乱序执行,如果a和b两个变量之间没有任何依赖关系,那么有可能是b先执行,而a后执行,因为不存在依赖关系,所以谁先谁后并不影响程序最终的结果。这就是所谓的指令重排序。
ok,接着我们继续分析下面稍加改动后的代码:
```
      int a=2;
      int b=1;
      int c=a+b;
     
```


这段代码里,不管a和b如何乱序执行,c的结果都是3,因为c变量依赖a和b变量,所以c变量是不会重排序到a或者b之前,a和b也不会重排到c之后,这其实是由happens-before关系里面的单线程下的as-if-serial语义限制的。

这里面还有一种特殊情况,需要注意一下:

```
        int a = 1;
        int b = 2;

        try {
            a = 3;           //A
            b = 1 / 0;       //B
        } catch (Exception e) {

        } finally {
            System.out.println("a = " + a);
        }

```


上面的例子中a和b变量,虽然没有依赖关系,但是在try-catch块里面发生了重排,b先执行,然后发生了异常,那么a的值最终还是3,由JVM保证在重排序发生异常的时候,在catch块里面作相关的特殊处理。这一点需要注意。



在单线程环境下,指令重排序是不会影响程序的最终执行结果的,但是重排序如果发生多线程环境下,就有可能影响程序正常执行,看下面的代码:


```
public class ReorderDemo1 {


    private int count=2;
    private boolean flag=false;
    private volatile boolean sync=false;

    public void write1()  {
        count=10;
        flag=true;//没有volatile修饰,实际执行顺序,有可能是flag=true先执行
    }

    public void read1()  {
        if(flag){
            System.out.print(count); // 有些jvm会打印10,有些jvm会打印2,这是不确定的
        }
    }


    public void write2() {
        count=10;
        sync=true;// 由于出现了volatile,所以这里禁止重排序
    }

    public void read2()  {
        if(sync){
            System.out.print(count); // 在jdk5之后,由volatile保证,count的值总是等于10
        }

    }




    public static void main(String[] args) {

        for(int i=0;i<300;i++){
            //实例化变量
            ReorderDemo1 reorderDemo1=new ReorderDemo1();
            //写线程
            Thread t1=new Thread(()-> { reorderDemo1.write1();});
            //读线程
            Thread t2=new Thread(()-> { reorderDemo1.read1(); });

             t1.start();
             t2.start();

        }




    }




}
```


上面的代码里面,有三个成员变量,其中最后一个是用volatile修饰的,有2对方法:


第一对方法里面:
```
    private int count=2;
    private boolean flag=false;
    private volatile boolean sync=false;

    public void write1()  {
        count=10;
        flag=true;//没有volatile修饰,实际执行顺序,有可能是flag=true先执行
    }

    public void read1()  {
        if(flag){
            System.out.print(count); // 有些jvm会打印10,有些jvm会打印2,这是不确定的
        }
    }
```

上面的代码,由于指令会重排序,当线程一里面执行write1方法的flag=true的时候,同时线程2执行了read1
方法,那么count的值是不确定的,可能是10,也可能是2,这个其实和操作系统有很大关系,如果cpu不支持指令重排,那么就不会出现问题,比如在X86的CPU上运行代码测试,可能不会出现多个值,但这不能说明其他的操作系统也不会出现。指令重排序在多线程环境下会带来不确定性,想要正确的使用,需要理解JMM内存模型。


第二对方法里面:

```
    private int count=2;
    private boolean flag=false;
    private volatile boolean sync=false;
    
     public void write2() {
     count=10;
     sync=true;// 由于出现了volatile,所以这里禁止重排序
    }

    public void read2()  {
        if(sync){
            System.out.print(count); // 在jdk5之后,由volatile保证,count的值总是等于10
        }

    }

```


注意这里的sync变量是加了volatile修饰,意味着禁止了重排序,第一个线程调用write2方法时候,同样第二个线程在调用read2方法时候,如果sync=true,那么count的值一定是10,有朋友可能会说count变量没有用volatile修饰啊,如何保证100%可见性呢? 确实在jdk5之前volatile关键字确实存在这种问题,必须都得加volatile修饰,但是在jdk5及以后修复了这个问题,也就是在jsr133里面增强了volatile关键字的语义,volatile变量本身可以看成是一个栅栏,能够保证在其前后的变量也具有volatile语义,同时由于volatile的出现禁止了重排序,所以在多线程下仍然可以得到正确的结果。



总结:


在Java里面除了volatile有禁止重排序的功能,内置锁synchronized和并发包的Lock也都有同样的语义。同步手段解决的主要问题是要保证代码执行的原子性,有序性,可见性。内置锁和J.U.C的锁同时具有这三种功能,而volatile不能保证原子性,所以在必要的时候还需要配合锁一起使用,才能编写出正确的多线程应用。


jsr133详细介绍:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#volatile



为了方便更好的交流,互助,学习,讨论问题,欢迎加入我们的“攻城师互助学习交流群”微信群,为了保证交流环境,想加入的小伙伴,可关注公众号,然后后台发送关键词微信群,加我微信号,由我拉你进去。








分享到:
评论

相关推荐

    Java——volatile关键字详解

    volatile保证可见性和禁止指令重排序,底层是通过“内存屏障”来实现,但不保证原子性。 写入volatile变量相当于退出同步代码块,读取volatile变量相当于进入同步代码块。 volatile的使用场景 对变量的写入操作不...

    详解Java线程编程中的volatile关键字的作用

    主要介绍了Java线程编程中的volatile关键字的作用,针对其禁止进行指令重排序和读写内存方面着重讲解,需要的朋友可以参考下

    Java中Volatile关键字步骤

    基本概念 Java内存模型中的可见性,原子性和有序性 可见性:  可见性是一种复杂的...volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程时可见的。但是这里需要注意个问题,volatile只能

    java进阶13天资料.zip

    day08-线程状态,volatile关键字、原子性、并发包、死锁、线程池 day09-方法引用, Stream流,File类 , 递归 ,字节流 day10-字符流, 缓冲流、转换流、序列化流,打印流,属性集 day11-Socket网络编程、NIO day12-...

    解开Volatile的面纱V1.1

    有序性:为了提高程序的执行性能,编辑器和处理器都有可能会对程序中的指令进行重排序。 无论是在面试时,还是在实际开发中,高并发问题已经成为了现在的主旋律。并发问题的定位和重 现是一件很棘手且难以解决的...

    Java JDK 7学习笔记(国内第一本Java 7,前期版本累计销量5万册)

    11.1.5 synchronized与volatile 334 11.1.6 等待与通知 345 11.2 并行api 349 11.2.1 lock、readwritelock与condition 349 11.2.2 使用executor 357 11.2.3 并行collection简介 370 11.3 重点复习 373 ...

    java面试800题

    Q0040 Java关键字 "51个:abstract, boolean, break, byte, case, catch, char, class, const, continue, default, do, double, else, extends, final, finally, float, for, goto, if, implements, import, ...

    Java 语言基础 —— 非常符合中国人习惯的Java基础教程手册

    在 java 语言中,Java 程序的基本单位是类,也就是说:一个 Java 程序是由多个类组成 的。定义一个类与定义一个数据类型是有区别的。在程序设计语言中,把定义数据类型的能 力作为一种很重要的能力来对待。在面向...

    java面试题,180多页,绝对良心制作,欢迎点评,涵盖各种知识点,排版优美,阅读舒心

    重排序 48 【JVM】内存泄漏 49 【JVM】java虚拟机的区域如何划分,每一个区的动能? 49 程序计数器(Program Counter Register) 49 java虚拟机栈 50 java堆(Java Heap) 51 方法区 51 【JVM】JVM内存结构,GC垃圾...

    java经典面试2010集锦100题(不看你后悔)

    JAVA试题(100道) —————————————————————————————————————— 题目1: 下面不属于基本类型的是:c (选择1项) A) boolean B) long C) String D) byte 题目2:d 如下程序中:...

    java核心知识点整理.pdf

    25 JAVA8 与元数据.................................................................................................................................25 2.4. 垃圾回收与算法 .................................

    Android-Advance:安卓系统以及进阶教程

    面试基础算法题排序链表二叉树JavaJVM基础GC根对象有什么类加载机制,双亲委派模型原理Java基础final关键字可以修饰某种,作用分别是什么volatile可以修饰某种,作用分别是什么同步可以修饰某种,作用分别是什么典型...

    JAVA核心知识点整理(有效)

    25 JAVA8 与元数据.................................................................................................................................25 2.4. 垃圾回收与算法 .................................

    汪文君高并发编程实战视频资源全集

    │ 高并发编程第二阶段09讲、指令重排序,happens-before规则精讲.mp4 │ 高并发编程第二阶段10讲、volatile关键字深入详解.mp4 │ 高并发编程第二阶段11讲、volatile关键字总结.mp4 │ 高并发编程第二阶段12讲...

    汪文君高并发编程实战视频资源下载.txt

    │ 高并发编程第二阶段09讲、指令重排序,happens-before规则精讲.mp4 │ 高并发编程第二阶段10讲、volatile关键字深入详解.mp4 │ 高并发编程第二阶段11讲、volatile关键字总结.mp4 │ 高并发编程第二阶段12讲...

    javaSE代码实例

    16.6 volatile关键字的含义与使用 372 16.7 小结 373 第17章 高级线程开发 374 17.1 线程池的使用 374 17.1.1 线程池的基本思想 374 17.1.2 JavaSE 5.0中固定尺寸线程池的基本知识 374 17.1.3 自定义...

    C语言解析教程(原书第4版)(美) 凯利.pdf

    2.4 关键字 2.5 标识符 2.6 常量 2.7 字符串常量 2.8 操作符和标点符号 2.9 操作符的优先级和结合性 2.10 增值操作符和减值操作符 2.11 赋值操作符 2.12 例子:计算2的乘方 2.13 c系统 2.13.1 预处理器 2.13.2 标准...

Global site tag (gtag.js) - Google Analytics