您当前的位置: 首页 >  Java
  • 0浏览

    0关注

    1477博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

java并发编程(3)--线程 有序性 volatile

软件工程小施同学 发布时间:2021-02-07 14:59:39 ,浏览量:0

一、编译器和处理器常常会对指令做重排

计算机在执⾏程序时,为了提⾼性能,编译器和处理器常常会对指令做重排,⼀般分以下三种:

 

  • 单线程环境⾥⾯确保程序最终执⾏结果和代码顺序执⾏的结果⼀致;
  • 处理器在进⾏重排序时必须要考虑指令之间的数据依赖性;
  • 多线程环境中线程交替执⾏,由于编译器优化重排的存在,两个线程中使⽤的变量能否保证⼀致 性是⽆法确定的,结果⽆法预测。

 

volatile可以保证有序性,也就是防⽌指令重排序。

所谓指令重排序,就是出于优化考虑,CPU执⾏指令的顺序跟程序员⾃⼰编写的顺序不⼀致。就好⽐⼀ 份试卷,题号是⽼师规定的,是程序员规定的,但是考⽣(CPU)可以先做选择,也可以先做填空。

int x = 11; //语句1 
int y = 12; //语句2 
 x = x + 5; //语句3 
 y = x * x; //语句4

以上例⼦,可能出现的执⾏顺序有1234、2134、1342,这三个都没有问题,最终结果都是x = 16, y=256。

但是如果是4开头,就有问题了,y=0。

这个时候就不需要指令重排序。

 

 

 

观看下⾯代码,在多线程场景下,说出最终值a的结果是多少? 5或者6

我们采⽤ volatile 可实现禁⽌指令重排优化,从⽽避免多线程环境下程序出现乱序执⾏的现象

package thread;

public class ResortSeqDemo {

    int a=0;
    boolean flag=false;

    /*
    多线程下flag=true可能先执⾏,还没⾛到a=1就被挂起。
    其它线程进⼊method02的判断,修改a的值=5,⽽不是6。
    */

    public void method01(){
        // 下面两条指令的执行顺序可能是颠倒的
        a=1;
        flag=true;
    }

    public void method02(){

        if (flag){
            a+=5;
            System.out.println("*****最终值a: " + a);
        }
    }

    public static void main(String[] args) {

        ResortSeqDemo resortSeq = new ResortSeqDemo();

        new Thread(()->{
            resortSeq.method01();
        },"ThreadA").start();


        new Thread(()->{
            resortSeq.method02();
        },"ThreadB").start();

    }

}

 

package thread;

import java.util.concurrent.TimeUnit;

public class ResortSeqDemo2 {

    int a=0;
    boolean flag=false;

    /*
    多线程下flag=true可能先执⾏,还没⾛到a=1就被挂起。
    其它线程进⼊method02的判断,修改a的值=5,⽽不是6。
    */

    public void method01(){

        flag=true;

        try{
            TimeUnit.SECONDS.sleep(1);
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }

        a=1;

    }

    public void method02(){

        if (flag){
            a+=5;
            System.out.println("*****最终值a: " + a);
        }
    }

    public static void main(String[] args) {

        ResortSeqDemo2 resortSeq = new ResortSeqDemo2();

        new Thread(()->{
            resortSeq.method01();
        },"ThreadA").start();


        new Thread(()->{
            resortSeq.method02();
        },"ThreadB").start();

    }

}

 

 

package thread;

import java.util.concurrent.TimeUnit;

public class ResortSeqDemo3 {

    volatile int a=0;
    boolean flag=false;

    /*
    多线程下flag=true可能先执⾏,还没⾛到a=1就被 挂起。
    其它线程进⼊method02的判断,修改a的值=5,⽽不是6。
    */

    public void method01(){

        flag=true;
        // code1
        // code2
        try{
            TimeUnit.SECONDS.sleep(1);
        }
        catch (InterruptedException e){
            e.printStackTrace();
        }

        // volatile变量之前的代码都可以重组,以上代码执行完,才会执行a=1
        a=1;
        // volatile变量之后的代码都可以重组,a=1执行完,才会执行下面的代码

        // code3
        // code4

    }

    public void method02(){

        if (flag){
            a+=5;
            System.out.println("*****最终值a: " + a);
        }
    }

    public static void main(String[] args) {

        ResortSeqDemo3 resortSeq = new ResortSeqDemo3();

        new Thread(()->{
            resortSeq.method01();
        },"ThreadA").start();


        new Thread(()->{
            resortSeq.method02();
        },"ThreadB").start();

    }

}

 

 

 

二、为什么volatile 可实现禁⽌指令重排优化,从⽽避免多线程环境下程序出现乱序执 ⾏的现象?

说说它的原理

我们先来了解⼀个概念,内存屏障(Memory Barrier)⼜称内存栅栏,是⼀个CPU指令,volatile底层 就是⽤CPU的内存屏障(Memory Barrier)指令来实现的,它有两个作⽤

  • ⼀个是保证特定操作的顺序性
  • ⼆是保证变量的可⻅性。

由于编译器和处理器都能够执⾏指令重排优化。

所以,如果在指令间插⼊⼀条Memory Barrier则会告诉编译器和CPU,不管什么指令都不能和这条Memory Barrier指令重排序,也就是说通过插⼊内存屏障 可以禁⽌在内存屏障前后的指令进⾏重排序优化。

内存屏障另外⼀个作⽤是强制刷出各种CPU的缓存数 据,因此任何CPU上的线程都能读到这些数据的最新版本。

 

 

 

 

 

关注
打赏
1665320866
查看更多评论
立即登录/注册

微信扫码登录

0.0647s