happens-before 原则

从JDK 5开始,Java使用新的JSR-133内存模型,提供了 happens-before 原则来辅助保证程序执行的原子性、可见性以及有序性的问题,它是判断数据是否存在竞争、线程是否安全的依据,happens-before 原则内容如下:

1、次序原则:一个线程内,按照代码的顺序,写在前面的操作先行发生于写在后面的操作,也就是说前一个操作的结果可以被后续的操作获取(保证语义串行性,按照代码顺序执行)。比如前一个操作把变量x赋值为1,那后面一个操作肯定能知道x已经变成了1。

2、锁定原则:一个 unLock 操作先行发生于后面对同一个锁的 lock 操作(后面指时间上的先后)。

3、volatile 变量原则:对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作,前面的写对后面的读是可见的,这里的后面同样指时间上的先后。

4、传递原则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C。

5、线程启动原则(Thread start Rule):Thread对象的 start() 方法先行发生于此线程的每一个动作

6、线程中断原则(Thread Interruption Rule):
  a:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
    b:可以通过 Thread.interrupted() 检测到是否发生中断;
    c:也就是说你要先调用 interrupt() 方法设置过中断标志位,我才能检测到中断发生。

7、线程终止原则(Thread Termination Rule):线程中的所有操作都优先发生于对此线程的终止检测,我们可以通过 isAlive() 等手段检测线程是否已经终止执行。

8、对象终结原则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize()方法的开始------->对象没有完成初始化之前,是不能调用 finalized() 方法的。

JVM 的设计分为两部分:
1、一部分是面向我们程序员提供的,也就是 happens-before 原则,它通俗易懂的向我们程序员阐述了一个强内存模型,我们只要理解 happens-before 原则,就可以编写并发安全的程序了。
2、另一部分是针对 JVM 实现的,为了尽可能少的对编译器和处理器做约束从而提升性能,JMM 在不影响程序执行结果的前提下对其不做要求,即允许优化重排序,我们只要关注前者就好了,也就是理解 happens-before 原则即可,其他繁杂的内容由 JMM 规范结合操作系统给我们搞定,我们只写好代码即可。

案例说明

private int value =0;
public int getValue(){
    return value;
}
public int setValue(){
    return ++value;
}

问题描述:假设存在线程A和B,线程A先(时间上的先后)调用了 setValue() 方法,然后线程B调用了同一个对象的 getValue() 方法,那么线程B收到的返回值是什么?
答案:不一定。

分析 happens-before 原则(原则5,6,7,8可以忽略,和代码无关)
1、由于两个方法由不同线程调用,不满足一个线程的条件,不满足程序次序原则
2、两个方法都没有用锁,不满足锁定原则
3、变量没有使用 volatile 修饰,所以不满足 volatile 变量原则
4、传递原则肯定不满足
综上:无法通过 happens-before 原则推导出线程A happens-before 线程B,虽然可以确定时间上线程A优于线程B,但就是无法确定线程B获得的结果是什么,所以这段代码不是线程安全的。

注意:如果两个操作的执行次序无法从happens-before原则推导出来,那么就不能保证他们的有序性,虚拟机可以随意对他们进行重排序。

happens-before 原则之 volatile 变量原则

操作二:普通读写操作二:volatile读操作二:volatile写
操作一:普通读写可以重排可以重排不可以重排
操作一:volatile 读不可以重排不可以重排不可以重排
操作一:volatile 写可以重排不可以重排不可以重排

当第一个操作为 volatile读 时,不论第二个操作是什么,都不能重排序,这个操作保证了volatile读 之后的操作不会被重排到 volatile读 之前。
当第一个操作为 volatile写 时,第二个操作为 volatile读 时,不能重排。
当第二个操作为 volatile写 时,不论第一个操作是什么,都不能重排序,这个操作保证了 volatile写 之前的操作不会被重排到 volatile写 之后。

在每个 volatile读 操作的后面插入一个 LoadLoad 屏障、LoadStore 屏障
图一
在每个 volatile写 操作的后面插入一个 StoreStore 屏障、StoreLoad 屏障
图二

java 内存模型中定义的8种每个线程与自己的工作内存与主物理内存之间的原子操作:
read(读取)——>load(加载)——>use(使用)——>assign(赋值)——>store(存储)——>write(写入)——>lock(锁定)——>unlock(解锁)
闭环流程

通常 volatile 用于保存某个变量的 boolean 值或者 int 值。

下图是 volatile写 的案例,就是代码 write() 方法里面的赋值语句
说明

下图是 volatile读 的案例,就是代码 read() 方法里面的判断语句
说明

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值