首页 > 编程语言 >Thread专题(13) - java存储模型

Thread专题(13) - java存储模型

时间:2022-10-11 19:02:04浏览次数:72  
标签:happens 13 java Thread 存储 线程 JVM 排序 before

此文被笔者收录在系列文章 ​​​架构师必备(系列)​​ 中

存储模型

java语言规范规定了JVM要维护内部线程类似顺序化语意,只要程序的最终结果等同于它在严格的顺序环境中执行的结果。

对性能提升除了越来越高的时钟频率,还有不断提升的并行性--管道超标量体系结构执行单元,动态指令调度,试探性执行以及成熟的多级存储缓存。处理器也在向多核转变,原因在于时钟频率正在变得难以经济地获得提高,可以提升的只有硬件的并行性。在多线程的环境中,为维护正确性不得不产生很大的性以开销,这是通过使用同步完成的。

  • 平台的存储模型

在可共享内存的多处理器体系架构中,每个处理器都有它自己的缓存,并且周期性地与主内存协调一致。处理器架构提供了不同级别的缓存一致性。

一种架构的存储模型告诉了应用程序可以从它的存储系统中获得何种担保,同时详细定义了一些特殊的指令称为“存储关卡或栅栏”,用以在需要共享数据时,得到额外的存储协调保证,JVM会通过在适当的位置上插入存储关卡来解决 JMM与底层平台存储模型之间的差异化。

并行的程序不是顺序执行的,也没有任何处理器可以保证这一点,所以需要程序员认识到这一点。


  • 重排序

各种能够引起操作延迟或错续执行的不同原因,都可以归结为一类重排序。在没有正确同步的情况下,即使是最简单的并发程序,也很难推断它的行为。内存级的重排序会让程序的行为变得不可预期,同步抑制了编译器、运行时和硬件对存储操作的各种方式的重排序,否则这些重排序会破坏JVM提供的可见性保证。


  • Java存储模型的简介

java存储模型的定义是通过动作的形式进行描述的,所谓动作,包括变量和读和写、监视器加锁和释放锁、线程的启动和拼接。JVM为所有程序内部的动作定义了一个偏序关系,叫作happens-before。要想保证执行动作B的线程看到动作A的结果,A与B之间就必须满足happens-before关系,如果不满足,JVM可以对它们随意地重排序。happens-before的法则包括:

  • 程序次序法则:线程中的每个动作A都happens-before于该线程中的每一个动作B,其中,在程序中,所有的动作B都出现在动作A之后;
  • 监视器锁法则:对一个监视器锁的解锁happens-before 于每一个后续对同一监视器锁的加锁;
  • volatle变量法则:对volatile域的写入操作happens-bfore于每一个后续对同一域的读操作;
  • 线程启动法则:在一个线程里,对Thread.start的调用会happens-before 于每一个启动线程中的动作;
  • 线程终结法则:线程中的任何动作都happens-before于其他线程检测到这个线程已经终结,或者从Thread.join调用中成功返回,或者Thread.isAlive返回false;
  • 中断法则:一个线程调用另一个线程的interrupt happens-before于被中断的线程发现中断(通过抛出InterruptedException,或者调用isInterrupted和interrupted);
  • 终结法则:一个对象的构造函数的结束happens-before于这个对象finalizer的开始;
  • 传递性:如果A happens-before 于B,且Bhappens-before 于C,则Ahappens-before 于C.

安全发布

安全发布利益于JVM提供的保证,而为不正确发布带来风险的真正原因,是在“发布共享对象”与从“另一个线程访问它”之间,缺少happens-before排序。

  • 不安全的发布

在缺少happens-before关系的情况下,存在重排序的可能性,这就解释了为什么如果在没有充分同步的情况下发布一个对象,会导致另外的线程看到一个部分创建的对象。

除了不可变对象以外,使用被另一个线程初始化的对象,是不安全的,除非对象的发布是happens-before于对象的消费线程使用它。

初始化安全性

初始化安全性保证只有通过final域触及的值,在构造函数完成时才是可见的,对于通过非final域触及的值,或者创建完成后可能改变的值,必须使用同步来确保可见性。

标签:happens,13,java,Thread,存储,线程,JVM,排序,before
From: https://blog.51cto.com/arch/5747714

相关文章