java多线程内存可见性以及Happens-before原则

指令重排,CPU的乱序执行

  • cpu允许将多条指令不按程序规定的顺序,分开发送给各相应电路单元处理的技术。
  • 在这期间不按照规定的顺序执行指令,然后由重新排列的单元将各执行单元结果按指令顺序重新排列。
  • 采用乱序执行技术的目的是为了使CPU内部电路满负荷运转并相应提高了CPU的运行程序的速度
    举例:A、B、C三个名人为晚会题写横幅“春节联欢晚会”六个大字,每人各写两个字。如果这时在一张大纸上按顺序由A写好"春节"后再交给B写"联欢",然后再由C写"晚会",那么这样在A写的时候,B和C必须等待,而在B写的时候C仍然要等待而A已经没事了。
    在这里插入图片描述
    但如果采用三个人分别用三张纸同时写的做法, 那么B和C都不必须等待就可以同时各写各的了,甚至C和B还可以比A先写好也没关系(就象乱序执行),但当他们都写完后就必须重新在横幅上(自然可以由别人做,就象CPU中乱序执行后的重新排列单元)按"春节联欢晚会"的顺序排好才能挂出去。

在这里插入图片描述
CPU(处理器)为了提高处理数据的速度,会进行乱序执行(out-of-orderexecution)。也就是重排序。但是CPU不会对任务操作进行重排序,编译器与处理器只会对没有数据依赖性的指令进行重排序。

数据依赖

如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。如下图所示:
在这里插入图片描述
上述三种情况,a与b存在着数据依赖性,同时大家也要注意。这里所说的数据依赖性是指单个处理器执行的指令序列和单个线程中执行的操作。多处理器和不同线程之间是没有数据依赖性这种关系的。

重新排序的规则

规则:不管怎么重新排序(编译器很热处理器为了提高并行速度),单线程(程序)执行结果不能被改变。、

double a = 3;//底
double h = 10;//高
double s = a*h/2//面积

在这里插入图片描述
如上图所示:a与s存在数据依赖关系,同时h与s也存在依赖关系。因此在程序的最终指令执行时。s是不能排在a与h之前。因为a与h不存在着数据依赖关系。所以处理器可以对a与h之前的执行顺序重排序。
在这里插入图片描述
经过处理器的重排序后,执行的结果并没有发生改变。

2.JAVA内存模型需要解决的问题

  • 工作内存的可见性问题
  • 重排序带来的问题
  • Happens-Before原则

2.1工作内存的可见性问题

工作内存的可见性问题(这里和计算机硬件的缓存不一致是一样的道理)。从上文的Java内存模型分析。我们已经知道了当多个线程操作同一个共享变量时,如果一个线程修改了其中的变量的值,另一个线程如何感知?

2.2重排带来的问题

CPU(处理器)的重排序会对多线程带来问题。

public class Demo {
    private int a = 0;
    private boolean isInit = false;
    private Config config;

    public void init() {
        config = readConfig();//1
        isInit = true;//2
    }
    public void doSomething() {
        if (isInit) {//3
            doSomethingWithconfig();//4
        }
    }
}

isInit用来标志是否已经初始化配置。其中1,2操作是没有数据依赖性,同理3、4操作也是没有数据依赖性的。那么CPU(处理器)可能对1、2操作进行重排序。对3、4操作进行重排序。现在我们加入线程A操作Init()方法,线程B操作doSomething()方法,那么我们看看重排序对多线程情况下的影响。
在这里插入图片描述上图中2操作排在了1操作前面。当CPU时间片转到线程B。线程B判断 if (isInit)为true,接下来接着执行 doSomethingWithconfig(),但是我们Config还没有初始化。所以在多线程的情况下。重排序会影响程序的执行结果。

3.Happens-Before原则(解决多线程共享变量可见性问题及产生的问题)

  1. 如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。

  2. 两个操作之间存在happens-before关系,并不意味着一定要按照happens-before原则制定的顺序来执行。如果重排序之后的执行结果与按照happens-before关系来执行的结果一致,那么这种重排序并不非法。

  • 如果一个操作的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系。
  • 避免工作内存不可见与多线程下指令重新排序带来的问题
  • Happens-Before原则是一组偏序关系:对于两个操作A和B,这两个操作可以在不同的线程中执行。如果A Happens-Before B,那么可以保证,当A操作执行完后,A操作的执行结果对B操作是可见的。
  • happens-before原则非常重要,它是判断数据是否存在竞争、线程是否安全的主要依据,依靠这个原则,解决在并发环境下两操作之间是否存在冲突的问题

下面是happens-before原则规则:

1.程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
2.锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
3.volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
4.传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则5.可以得出操作A先行发生于操作C;
6.线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
7.线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
8.线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
9.对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

3.1程序次序规则

在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。这是因为Java语言规范要求Java内存模型在单个线程内部要维护类似严格串行的语义,如果多个操作之间有先后依赖关系,则不允许对这些操作进行重排序。故而这个规则只对单线程有效,在多线程下无法保证正确性。

3.2锁定规则

无论是在单线程还是多线程环境,一个锁处于被锁定的状态,那么必须先执行unlock解锁操作后,才能lock上锁操作。

public class Demo {
    private int value;
    public synchronized void setValue(int value) {
        this.value = value;
    }
    public synchronized int getValue() {
        return value;
    }
}

上面这段代码,setValue与getValue拥有同一个锁(也就是当前实例对象),假设setValue方法在线程A中执行,getValue方法在线程B中执行。线程A调用setValue方法会先对value变量赋值,然后释放锁。线程B调用getValue方法会先获取到同一个锁后,再读取value的值。那么B线程获取的value的值一定是正确的。

3.3volatile变量规则

对一个volatile变量的写操作,先行发生于后面这个变量的读操作。、
volatile保证了线程的可见性,通俗点将就是如果一个线程先去写一个volatile变量,然后一个线程去读这个变量,那么这个写操作一定是happens-before读操作的。

public class Demo {
    private volatile boolean flag;
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    public boolean isFlag() {
        return flag;
    }
}

3.4线程启动规则

Thread对象的start()方法先行发生于此线程的每个动作。
假定线程A在执行过程中,通过执行ThreadB.start()来启动线程B,那么线程A对共享变量的修改在接下来线程B开始执行后确保对线程B可见。
start方法和新线程中的动作一定是在两个不同的线程中执行。线程启动规则可以这样去理解:调用start方法时,会将start方法之前所有操作的结果同步到主内存中,新线程创建好后,需要从主内存获取数据。这样在start方法调用之前的所有操作结果对于新创建的线程都是可见的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值