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

关于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关键字解析

    ### Java并发编程:volatile关键字解析 #### 一、内存模型的相关概念 在深入了解`volatile`关键字之前,我们首先需要理解计算机内存模型的一些基本概念。在现代计算机系统中,CPU为了提高执行效率,会将频繁访问的...

    深入解析Java中的volatile关键字:原理、应用与实践

    volatile关键字是Java并发编程中一个重要的工具,它通过确保变量的可见性和禁止指令重排序来提高程序的并发性能。然而,由于volatile不提供原子性,因此在进行复合操作时,仍然需要使用其他同步机制。通过理解...

    java里的volatile关键字详解

    3. 有序性:volatile关键字可以保证线程之间操作的有序性,因为其本身包含“禁止指令重排序”的语义。 三、 volatile 和synchronized关键字的比较 1. volatile和synchronized都是Java中的同步机制,用于保证线程...

    一文精通Java中的volatile关键字

    Java中的`volatile`关键字是多线程编程中的一个重要概念,它的主要作用是确保共享变量的可见性和禁止指令重排序。本文将深入探讨`volatile`的关键特性、工作原理以及使用注意事项。 1. 可见性: `volatile`关键字...

    Java中volatile关键字的总结.docx

    Java中的`volatile`关键字是用于处理并发编程中线程间通信和可见性的重要工具。它是一种轻量级的同步机制,比`synchronized`锁更为高效,因为它不会导致线程上下文的切换和调度,减少了不必要的开销。然而,`...

    深入了解Java中Volatile关键字

    2. 禁止指令重排序:volatile关键字具有“禁止指令重排序”的语义,保证了操作的有序性。 三、Volatile关键字的使用 1. 使用volatile修饰变量:volatile关键字可以修饰变量,使得该变量具有可见性。 2. 使用...

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

    Volatile关键字可以禁止指令重排序,保证线程之间操作的有序性。 在Java中,Volatile关键字可以用于解决可见性问题,例如在多线程环境下,一个线程修改的变量对另一个线程是可见的。但是,Volatile关键字不能解决...

    Java——volatile关键字详解

    `volatile`关键字的主要作用是确保多线程环境下的可见性和禁止指令重排序,但不保证原子性。 **volatile的可见性:** 在Java中,线程有自己的工作内存,每个线程可能会有自己的副本变量,而`volatile`关键字可以...

    深入理解 volatile 关键字.doc

    volatile关键字是Java语言的高级特性,它可以保证可见性和禁止指令重排序,但是要弄清楚其工作原理,需要先弄懂Java内存模型。 保证可见性 volatile关键字可以保证可见性,即当一个线程修改了某个变量时,其他所有...

    解析java中volatile关键字

    Java编译器和运行时为了优化性能,可能会对代码进行重排序,但`volatile`变量的读写操作不会被重排序,确保了读写操作的顺序对所有线程都是可见的。这保证了一种叫做“单线程观察”的一致性,即任何线程看到的`...

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

    Java中的`volatile`关键字是一个非常重要的并发编程工具,它的作用主要体现在两个方面:**可见性**和**有序性**。本文将深入解析`volatile`的关键字特性及其在实际编程中的应用。 1. 可见性: 当一个共享变量被`...

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

    原子性指的是对基本数据类型的读取和赋值操作是原子性操作,volatile关键字可以保证共享变量的可见性,volatile关键字也可以禁止指令重排序,保证操作的有序性。 volatile关键字的使用场景: volatile关键字主要...

    多方面解读Java中的volatile关键字.rar

    2. **有序性**:`volatile`保证了指令重排序不会影响到`volatile`变量的读写。它阻止了编译器和处理器为了优化性能而对指令序列进行重新排序,确保了对`volatile`变量的读写操作具有一定的顺序性。但这并不意味着`...

    java volatile关键字使用方法及注意事项

    volatile关键字在Java编程语言中扮演着重要的角色,特别是在多线程环境下的同步和可见性问题。它是Java内存模型(JMM)的一部分,用于确保共享变量的可见性和有序性,但不保证原子性。 1. **volatile的可见性**:当...

    深入了解java并发的volatile关键字的底层设计原理.docx

    2. **有序性**:`volatile`关键字还能防止编译器和处理器对操作`volatile`修饰的变量进行指令重排序。 二、缓存一致性协议 要深入理解`volatile`的底层实现机制,我们需要先了解计算机系统中的缓存一致性协议。现代...

    深入理解Java中的volatile关键字(总结篇)

    有序性是指Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性,volatile是因为其本身包含“禁止指令重排序”的语义,synchronized是由“一个变量在同一个时刻只允许一条线程对其进行lock...

    详细分析java并发之volatile关键字

    volatile 关键字的语义是告诉系统说我容易变化,编译器你不要随便优化(重排序、缓存)我。当一个变量被修饰为 volatile 时,编译器将不会对该变量进行读写优化,这样可以确保线程之间的可见性和有序性。 happens-...

    面试官最爱的volatile关键字.docx

    在Java编程中,volatile关键字是一个至关重要的概念,尤其在多线程环境下,理解并正确使用volatile是面试时必不可少的知识点。volatile被视为synchronized的一种轻量级实现,但两者在特性上存在显著差异。 首先,...

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

    在Java的JVM中,为了优化性能,编译器和处理器可能会对指令进行重排序,但`volatile`保证了对变量的读写操作不会被重排序,从而确保了多线程环境下的程序执行顺序。 然而,尽管`volatile`提供了这些保证,但它并不...

    Java里volatile关键字是什么意思

    2. **有序性**:`volatile`保证了指令重排序不会影响到`volatile`变量的读写。它实现了一个“读屏障”(Load Barrier)和“写屏障”,防止编译器或者处理器优化时对`volatile`变量的读写操作进行重新排序,从而确保...

Global site tag (gtag.js) - Google Analytics