美文网首页
Java并发编程的艺术-Java内存模型

Java并发编程的艺术-Java内存模型

作者: 油多坏不了菜 | 来源:发表于2019-02-24 22:36 被阅读0次

基础

  • 共享变量(堆空间中所有的实例域,静态域,数组元素)的访问需要同步,而局部变量不会在线程间共享,所以不存在可见性问题。
  • 每一个线程都有一个私有的本地内存(抽象概念)
  • 源代码到最终的指令序列执行需经过编译器重排序和处理器重排序
  • JMM要求java编译器在生成指令序列时,插入特定的内存屏障以禁止特定类型的处理器重排序。

重排序

  • 重排序时遵守数据依赖性(写后读,读后写,写后写)
  • as-if-serial语义:不管怎么重排序,确保单线程程序的执行结果不变。

volatile的内存语义

  • 可见性:对一个volatile变量的读,总是能读到对这个volatile变量最后的写入
  • 原子性:对单个volatile变量的读写具有原子性(volatile++这种复合操作不具有原子性)
  • A线程在写volatile变量之前所有可见的共享变量,在B线程读同一个volatile变量后,将立即对B线程可见.

锁的内存语义

  • 线程A在释放锁之前所有可见的共享变量,在线程B获取同一个锁之后,将立刻对B线程可见。
  • 锁释放与volatile写有相同的内存语义;锁获取与volatile读有相同的内存语义

final内存语义

  • 写final域的重排序规则禁止把final域的写重排序到构造函数之外。这点保证了对象引用为任意线程可见时,对象的final域已经被正确初始化
  • 读final域的重排序规则指出,初次读对象引用与初次读该对象包含的final域之间不能重排序
  • 对象引用不能在构造函数中逸出

hanppens-before规则

如果A happens before B,那么A的执行结果对B可见。(java中 并不一定表示A比B先执行,如果A与B执行的顺序对结果没有影响是可以重排序的)

  • 程序顺序规则
  • 监视器锁规则:对锁的解锁,happens before 随后对该锁的加锁
  • volatile规则:对一个volatile变量的写happens before 随后对该volatile变量的读
  • 传递性
  • start()规则:如果线程A中执行ThreadB.start(),那么A线程的ThreadB.start() happens before 线程B中的操作
  • join规则:如果线程A执行ThreadB.join并成功返回,那么线程B中的操作happens before A中从ThreadB.join()操作成功返回。(线程B对共享变量的改变对A线程可见)

双锁检查锁定与延迟初始化

  1. 有问题的demo
private static Instance ins;
public static Instance getInstance(){
if(ins == null){
   synchronized(ins){
    if(ins == null)
      ins = new Insatance();//这一步不是原子操作
  }
}
return ins;

上面问题出在 ins = new Instance()这句不是原子操作,而且其中可能存在重排序,所以 ins变量不为null时,对象可能还没有被初始化.

  1. 基于volatile的解决方案
    对实例字段的延迟初始化
    当把对象声明为 volatile时,可以禁止重排序,保证getInstance()方法返回的对象一定是已经被正确初始化了的。
  2. 基于类初始化的解决方案
    对静态字段的延迟初始化。
public class InstanceFactory{
    private static class InstanceHoler{
      public static Ins = new Instance();
   }
public static Instance getInstance(){
     return InstanceHolder.ins;
 }
}

相关文章

网友评论

      本文标题:Java并发编程的艺术-Java内存模型

      本文链接:https://www.haomeiwen.com/subject/nobbyqtx.html