第16章.Java内存模型

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第16章.Java内存模型相关的知识,希望对你有一定的参考价值。

  假设一个线程为变量赋值:variable = 3;

  内存模型需要解决一个问题:“在什么条件下,读取variable的线程将看到这个值为3?”

  这看上去理所当然,但是如果缺少同步,那么将会有许多因素使得线程无法立即甚至永远,看到另一个线程的操作结果。如:

  1.在编译器中生成的指令顺序,可以与源代码中的顺序不同,此外编译器还会将变量保存在寄存器而不是内存中;

  2.处理器可以采用乱序或并行等方式来执行指令;

  3.缓存可能会改变将写入变量提交到主内存的次序;

  4.而且保存在处理器本地缓存中的值,对于其他处理器是不可见的。

  这些因素都会使得一个线程无法看到变量的最新值,并且会导致其他线程中的内存操作似乎在乱序执行。

 

  Java语言规范要求JVM在线程中维护一种类似串行的语义:只要程序的最终结果与严格串行环境中执行的结果相同,那么上述所有的操作都是允许的。

  这确实是一件好事,因为计算机近年来在性能上的提升很大程度要归功于这些重新排序措施。

 

  在单线程环境中,我们无法看到所有这些底层技术,它们除了提高程序的执行速度外,不会产生其他影响。

  在多线程环境中,要维护程序的串行性将导致很大的性能开销。对于并发应用程序中的线程来说,它们在大部分时间里都执行各自的任务,因此在线程之间的协调操作只会降低应用程序的运行速度,而不会带来任何好处。只有当多个线程要共享数据时,才必须协调它们之间的操作,并且JVM依赖程序通过同步操作来找出这些协调操作将在何时发生。

 

  JVM规定了一组最小保证,这组保证规定了对变量的写入操作将在何时对于其他线程可见。JVM在设计时就在可预测性和程序的易于开发性之间进行了权衡,从而在各种主流的处理器体系架构上都能实现高性能的JVM。

平台的内存模型

在共享内存的多处理器体系架构中,每个处理器都拥有自己的缓存,并且定期地与主内存进行协调。

在不同的处理器架构中提供了不同级别的缓存一致性,其中一部分只提供最小的保证,即允许不同的处理器在任意时刻从同一个存储位置上看到不同的值。

 

要想确保每个处理器都能在任意时刻知道其他处理器正在进行的工作,将需要非常大的开销。在大多数时间里,这种信息是不必要的,因此处理器会适当放宽存储一致性保证,以换取性能的提升。

在架构定义的内存模型中将告诉应用程序可以从内存系统中获得怎样的保证,此外还定义了一些特殊的指令(称为内存栅栏),当需要共享数据时,这些指令就能实现额外的存储协调保证。为了使Java开发人员无须关心不同架构上内存模型之间的差异,Java还提供了自己的内存模型,并且JVM通过在适当的位置插上内存栅栏来屏蔽在JVM与底层平台内存模型之间的差异。

 

 假设:想象在程序中只存在唯一的操作执行顺序,而不考虑这些操作在何种处理器上执行,并且在每次读取变量时,都能获得在执行序列中(任何处理器)最近一次写入该变量的值。

这种乐观的模型被称为串行一致性,开发人员经常会错误的假设存在串行一致性,但在任何一款现代多处理器架构中都不会提供这种串行一致性,JVM也如此。

在支持共享内存的多处理器和编译器中,当跨线程共享数据时,会出现一些奇怪的情况,除非通过使用内存栅栏来防止这些情况的发生。不过在Java程序中不需要指定内存栅栏的位置,而只需要通过正确地使用同步来找出何时将访问共享状态。

重排序

/**
 * 
@author 83921
 * 在没有正确同步的情况下,即使要推断最简单的并发程序的行为也很困难。在示例中:
 * 很容易想象如何输出(1,0),(0,1)或(1,1),T1可以在T2开始之前完成,T2也可以在T1开始之前完成,或者二者交替执行。
 * 但还可以输出(0,0),由于每个线程中的各个操作之间不存在数据流依赖性,因此这些操作可以乱序执行(即使这些操作按照顺序执行,但在将
 * 缓存刷新到主内存的不同时序中也可能出现这种情况,在T2的角度看,T1的赋值操作可能以相反的次序执行)。
 * 可以想象在T2看来的执行顺序[ x=b, b=1, y=a, a=1 ]
 * 要列举出这个简单示例的所有可能执行结果非常困难,内存级的重排序会使程序的行为变得不可预测。
 * 而要确保在程序中正确地使用同步却非常容易,同步将限制 编译器、运行时和硬件对内存操作重排序的方式,从而在重排序时不会破坏JVM提供的可见性保证。
 
*/
public class Demo{
    
    static int x = 0, y = 0;
    static int a = 0, b = 0;
    
    public static void main(String[] args) throws InterruptedException {
        
        Thread T1  = new Thread(new Runnable(){
            public void run() {
                a = 1;
                x = b;
            }
        });
        
        Thread T2 = new Thread(new Runnable(){
            public void run() {
                b = 1;
                y = a;
            }
        });
        
        T1.start();
        T2.start();
        
        T1.join();
        T2.join();
        
        System.out.println(x +"--"+y);
    }
}

 Java内存模型

java内存模型是通过各种操作来定义的,包括对变量的读/写操作,监视器的加锁和释放操作,以及线程的启动和合并操作。

JVM为程序中所有的操作定义了一个偏序关系,称为Happens-Before。要想保证执行操作B的线程看到操作A的结果(无论A和B是否在同一个线程中执行),那么在A和B之间必须满足Happens-Before关系。如果缺乏这个关系,那么JVM可以对它们任意的重排序。

 

以上是关于第16章.Java内存模型的主要内容,如果未能解决你的问题,请参考以下文章

《深入理解java虚拟机》---第12章 java内存模型与线程

《java并发编程实战》读书笔记13--Java内存模型,重排序,Happens-Before,

《现代操作系统》—— 第7章 存储模型

JVM系列之内存模型(Java Memory Model)

JVM系列之内存模型(Java Memory Model)

java并发学习--第十章 java内存模型的内存语义