多线程。Java内存模型(第1部分)

哈Ha!我提请您注意Jakob Jenkov撰写的文章“ Java Memory Model”的第一部分



我正在接受Java培训,需要学习Java内存模型文章为了更好地理解,我进行了翻译,但是为了避免失去好处,我决定与社区分享。我认为这对初学者很有用,如果有人喜欢,我会翻译其余的内容。



原始的Java内存模型不是很好,因此在Java 1.5中对其进行了修订。该版本今天仍在使用(Java 14+)。





内部Java内存模型



JVM内部使用的Java内存模型将内存分为线程堆栈和堆。该图从逻辑角度说明了Java内存模型:



图片



Java虚拟机中运行的每个线程都有自己的堆栈。堆栈包含有关线程调用哪些方法以达到当前执行点的信息。我将其称为“调用堆栈”。线程一旦执行其代码,调用堆栈就会更改。



线程堆栈包含每个执行的方法(调用堆栈中的所有方法)的所有局部变量。线程只能访问自己的堆栈。局部变量对创建线程的所有其他线程不可见。即使两个线程执行相同的代码,它们仍将在自己的堆栈上创建该代码的局部变量。因此,每个线程对每个局部变量都有其自己的版本。



所有原始类型的本地变量(布尔,字节,短型,字符,整数,长型,浮点型,双精度型)都完全存储在线程堆栈上,其他线程看不到。一个线程可以将原始变量的副本传递给另一个线程,但是不能共享原始局部变量。



堆包含在Java应用程序中创建的所有对象,而与创建该对象的线程无关。这包括原始类型对象的版本(例如Byte,Integer,Long等)。创建对象并将其分配给局部变量还是创建为另一个对象的成员变量都没有关系,它存储在堆中。



这是说明调用堆栈和存储在线程堆栈上的局部变量以及存储在堆上的对象的图:



图片



局部变量可以是原始类型,在这种情况下,它完全存储在线程的堆栈中。



局部变量也可以是对象引用。在这种情况下,引用(局部变量)存储在线程堆栈上,但是对象本身存储在堆上。



一个对象可以包含方法,而这些方法可以包含局部变量。即使将拥有该方法的对象存储在堆中,这些局部变量也存储在线程堆栈中。



对象的成员变量与对象本身一起存储在堆中。当成员变量是原始类型时,以及当它是对象引用时,都是如此。



静态类的变量也与类定义一起存储在堆中。



引用对象的所有线程都可以访问堆上的对象。当线程可以访问对象时,它也可以访问该对象的成员变量。如果两个线程同时在同一个对象上调用一个方法,则它们都将有权访问该对象的成员变量,但是每个线程将拥有自己的局部变量副本。



这是说明上述要点的图:



图片



两个线程具有一组局部变量。局部变量2指向堆上的共享对象(对象3)。也就是说,每个线程都有自己的局部变量副本和自己的引用。因此,两个不同的引用指向堆上的同一对象。



请注意,通用对象3引用了对象2和对象4作为成员变量(如箭头所示)。通过这些链接,两个线程可以访问对象2和对象4。



该图还显示了局部变量(局部变量1)。它的每个副本都包含不同的引用,这些引用指向两个不同的对象(对象1和对象5),而不是同一对象。从理论上讲,如果两个线程都引用了这两个对象,则它们都可以访问对象1和对象5。但是在上图中,每个线程仅引用两个对象之一。



那么这些说明可能会产生什么样的Java代码?好吧,简单的代码如下所示:



Public class MyRunnable implements Runnable() {

    public void run() {
        methodOne();
    }

    public void methodOne() {
        int localVariable1 = 45;

        MySharedObject localVariable2 =
            MySharedObject.sharedInstance;

        //... do more with local variables.

        methodTwo();
    }

    public void methodTwo() {
        Integer localVariable1 = new Integer(99);

        //... do more with local variable.
    }
}


public class MySharedObject {

    // ,    MySharedObject

    public static final MySharedObject sharedInstance =
        new MySharedObject();


    // -,      

    public Integer object2 = new Integer(22);
    public Integer object4 = new Integer(44);

    public long member1 = 12345;
    public long member2 = 67890;
}


run()方法调用methodOne(),methodOne()调用methodTwo()。



methodOne()声明一个int类型的原始局部变量(localVariable1)和一个作为对象引用的局部变量(localVariable2)。



每个执行One()方法的线程将在各自的堆栈上创建其自己的localVariable1和localVariable2副本。 localVariable1变量将彼此完全分开,位于每个线程的堆栈中。一个线程看不到另一个线程对其localVariable1副本所做的更改。



每个执行One()方法的线程也会创建自己的localVariable2副本。但是,localVariable2的两个不同副本最终指向堆上的同一对象。关键是localVariable2指向sharedInstance静态变量引用的对象。静态变量只有一个副本,并且该副本存储在堆中。因此,localVariable2的两个副本最终都指向相同的MySharedObject实例。MySharedObject实例也存储在堆中。它对应于上图中的对象3。



请注意,MySharedObject类还包含两个成员变量。成员变量本身与对象一起存储在堆中。这两个成员变量指向另外两个Integer对象。这些整数对象对应于图中的对象2和对象4。



还要注意,methodTwo()创建了一个名为localVariable1的局部变量。此局部变量是对Integer对象的引用。该方法将引用localVariable1设置为指向新的Integer实例。该链接将存储在每个线程自己的localVariable1副本中。这两个Integer实例将存储在堆上,并且由于该方法每次执行时都会创建一个新的Integer对象,因此执行此方法的两个线程将创建单独的Integer实例。它们对应于上图中的对象1和对象5。



还要注意类型为long的MySharedObject类中的两个成员变量,该类型是基本类型。由于这些变量是成员变量,因此它们仍与对象一起存储在堆中。仅局部变量存储在线程堆栈上。



第2部分在这里。



All Articles