Java内存模型

并发、缓存一致性、内存模型

Posted by Booogu on February 26, 2021
1306 字 4 分钟

为什么要争取并发,让计算机同时去做几件事?

这是人类压榨计算机运算能力的最有力武器,不仅是因为计算机运算能力太强大,还因为计算机的运行速度与它的存储和通信子系统速度差距太大,大量时间都花费在磁盘IO、网络通信或者数据访问上,所以为了不让处理器在大部分时间都花费在等待其他资源的空闲状态,就必须使用一些手段去把处理器的运算能力”压榨“出来。

缓存一致性问题

因为处理器运算和其他存储设备速度差异过大,所以需要引入高速缓存,它们和主内存之间,存在缓存一致性问题。 需要各个处理器在访问缓存时遵循一些协议,在读写时要根据协议操作,这类协议有MSI、MESI、MOSI、Synapse、Firely。Dragon Protocol等

Java内存模型

工作内存与主内存,对应高速缓存与主内存 注意:Java线程的工作内存中保存了该线程使用的变量的主内存副本:注意只是主内存部分数据(对象的引用、对象中在某个线程访问到的字段是有可能被复制的,但不会有虚拟机把整个对象复制一次)

Volatile关键字

适用场景官方定义:(满足其中之一就可以使用volatile,否则应该使用sychronize)

  • 运算结果并不依赖变量的当前值,或者能够确保只有单一线程修改变量的值 —— 后半句不用说,前半句举例(其实是在说,不需要原子性):i=1,不依赖i当前的值;而I = I +1则依赖当前i的值,因为不是一个原子操作
  • 变量不需要与其他状态变量共同参与不变约束(??)

原子性、可见性、有序性

  • 原子性:同步块synchronized关键字实现
  • 可见性:除了volatile外,还有synchronized和final能实现,final:被final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把this的引用传递出去,那么在其他线程中就能看到final字段的值(无需同步,就能被其他线程正确访问,??)
  • 有序性:定义:如果在本线程内观察,所有操作都是有序的,如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句是指”线程内似表现为穿行的语义(within-thread as-if-serial Semantics)“;后半句是指”指令重排序“现象和”工作内存与主内存存在同步延迟“现象。

Volatile为何能保证有序性与可见性

  • 有序性:带有此关键字的变量赋值后,字节码会多一个lock add1 $0x0,(%esp)操作,作用相当于一个内存屏障(指令重排序时,不能把后面的指令重排序到内存屏障之前);
  • 可见性:修改值后,会立即更新到主内存,同时,其他处理器或别的内核的工作内存会被无效化(invalidate),相当于做了依次store 和 write(通知并写入主存)

如何判断并发是否安全?

先行发生原则(Happens Before): 指java内存模型中定义的两项操作之间的偏序关系,比如说操作A先行发生于操作B,其实就是说在发生操作B之前,操作A造成的影响能被B观察到。

Java内存中天然存在的先行发生关系:

  • 程序次序规则
  • 管程锁定规则
  • volatile变量规则
  • 线程启动规则
  • 线程终止规则
  • 线程中断规则
  • 对象终结规则
  • 传递性

结论: 时间先后顺序与先行发生原则之间基本没有任何因果关系