一文搞定JMM核心原理

您可以使用JAVA synchronized块 。同步块保证在任何给定时间只有一个线程可以进入代码的给定关键部分 。同步块还保证在同步块内访问的所有变量都将从主存储器中读入,当线程退出同步块时 , 所有更新的变量将再次刷新回主存储器,无论变量是不是声明为volatile 。JMM引入从堆栈说起JVM内部使用的Java内存模型在线程栈和堆之间划分内存 。此图从逻辑角度说明了Java内存模型:

一文搞定JMM核心原理

文章插图
图片
# 堆栈里面放了什么?线程堆栈还包含正在执行的每个方法的所有局部变量(调用堆栈上的所有方法) 。线程只能访问它自己的线程堆栈 。由线程创建的局部变量对于创建它的线程以外的所有其他线程是不可见的 。即使两个线程正在执行完全相同的代码,两个线程仍将在每个自己的线程堆栈中创建该代码的局部变量 。因此,每个线程都有自己的每个局部变量的版本 。
基本类型的所有局部变量(boolean , byte,short,char , int,long,float,double)完全存储在线程堆栈中,因此对其他线程不可见 。一个线程可以将一个基本类型变量的副本传递给另一个线程,但它不能共享原始局部变量本身 。
堆包含了在Java应用程序中创建的所有对象,无论创建该对象的线程是什么 。这包括基本类型的包装类(例如Byte,Integer,Long等) 。无论是创建对象并将其分配给局部变量,还是创建为另一个对象的成员变量 , 该对象仍然存储在堆上 。
一文搞定JMM核心原理

文章插图
图片
局部变量可以是基本类型,在这种情况下 , 它完全保留在线程堆栈上 。
【一文搞定JMM核心原理】局部变量也可以是对象的引用 。在这种情况下 , 引用(局部变量)存储在线程堆栈中 , 但是对象本身存储在堆(Heap)上 。
对象的成员变量与对象本身一起存储在堆上 。当成员变量是基本类型时,以及它是对象的引用时都是如此 。
静态类变量也与类定义一起存储在堆上 。
线程栈如何访问堆上对象?所有具有对象引用的线程都可以访问堆上的对象 。当一个线程有权访问一个对象时,它也可以访问该对象的成员变量 。如果两个线程同时在同一个对象上调用一个方法,它们都可以访问该对象的成员变量,但每个线程都有自己的局部变量副本 。
一文搞定JMM核心原理

文章插图
图片
两个线程有一组局部变量 。其中一个局部变量(局部变量2)指向堆上的共享对象(对象3) 。两个线程各自对同一对象具有不同的引用 。它们的引用是局部变量,因此存储在每个线程的线程堆栈中(在每个线程堆栈上) 。但是 , 这两个不同的引用指向堆上的同一个对象 。
注意共享对象(对象3)如何将对象2和对象4作为成员变量引用(由对象3到对象2和对象4的箭头所示) 。通过对象3中的这些成员变量引用 , 两个线程可以访问对象2和对象4.
该图还显示了一个局部变量 , 该变量指向堆上的两个不同对象 。在这种情况下 , 引用指向两个不同的对象(对象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 {//static variable pointing to instance of MySharedObjectpublic static final MySharedObject sharedInstance =new MySharedObject();//member variables pointing to two objects on the heappublic Integer object2 = new Integer(22);public Integer object4 = new Integer(44);public long member1 = 12345;public long member1 = 67890;}如果两个线程正在执行run()方法,则前面显示的图表将是结果 。run()方法调用methodOne() , methodOne()调用methodTwo() 。
methodOne()声明一个局部基本类型变量(类型为int的localVariable1)和一个局部变量,它是一个对象引用(localVariable2) 。
执行methodOne()的每个线程将在各自的线程堆栈上创建自己的localVariable1和localVariable2副本 。localVariable1变量将完全相互分离 , 只存在于每个线程的线程堆栈中 。一个线程无法看到另一个线程对其localVariable1副本所做的更改 。


推荐阅读