Java多线程与高并发二(JMM)
JMM-Java内存模型
Java虚拟机把Java的内存分为thread stacks和heap,在理解多线程的时候,这种粗糙的分法已足够。
如图所示,该图有如下含义:
1、每个线程都有自己的thread stack,意味着只要是存在于thread stack的变量都是线程独有,别的线程是不能访问的。说的直白点,即使两个线程执行相同代码,每个线程都独享变量。
2、Java中的基础数据类型(boolean
, byte
, short
, char
, int
, long
, float
, double
)都是存在于thread stack中。
3、heap中包含了代码中所有new出来的对象或者通过其他手段搞出来的对象,注意:即使new出来的对象是某个其他对象的成员变量引用的,也会在堆中存储。
要深入理解Java变量和对象是怎么存储在内存中的,我们可以... talk is cheap, show me the code:
public class TestMemoryModel {
public static final TestMemoryModel sharedInstance = new TestMemoryModel();
int i1 = 6;
Integer integer1 = new Integer(666);
{
long long1 = 11111;
long long2 = 22222;
}
public TestMemoryModel() {
long long3 = 33333;
long long4 = 44444;
}
public void m1() {
Integer object2 = new Integer(11);
Integer object4 = new Integer(22);
}
}
代码中,总结一下几点:
1、局部变量(语句块、构造方法、普通方法中的变量)如果是基础类型,比如long1,long2,都存储在栈中。
2、局部变量是引用类型,变量依然存在栈中,对象(Integer(11)、Integer(22))是存在堆中。
3、对象中包含的方法,而方法中的局部变量是存在栈中的,即使整个对象是在堆中。
4、对象中的成员变量(i1,integer1),不论是基础数据类型还是引用类型,都是随对象存在于堆中。
5、类变量(声明为static的变量)也是随对象存在于堆中。
现在我们已经知道变量和对象怎么存储在内存中,那么如下的图对应代码应该是怎么样的呢?
public class MyRunnable implements Runnable() {
public void run() {
methodOne();
}
public void methodOne() {
int localVariable1 = 45;
MySharedObject localVariable2 = MySharedObject.sharedInstance;
methodTwo();
}
public void methodTwo() {
Integer localVariable1 = new Integer(99);
}
}
public class 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 member1 = 67890;
}
计算机内存架构
计算机内存跟Java内存模型有一些区别
计算机的内存大体分为三部分:
1、CPU内部的高速寄存器
2、CPU缓存
3、主内存(RAM)
读写速度而言,寄存器>CPU缓存>主内存
CPU缓存,也有可能有多级,一级缓存、二级缓存等。
CPU需要读取主内存的时候,通常会把主内存的部分数据读取到缓存中,甚至也可以读到寄存器中,再进行操作。当CPU处理完后,要把结果从寄存器刷新到缓存中,再在某个时候刷新回主内存。
JVM如何处理计算机内存架构和JMM的不同?
JMM把内存分为堆和栈,计算机的内存又分为主内存、高速缓存、CPU寄存器。
堆栈的分区可能出现在内存架构中的各个模块。
那么在代码实际运行中,会有一些问题发生:
1、线程A读取数据到高速缓存中,CPU从高速缓存把线程A读取的数据加工后,还没有刷新回主内存的时候,线程B实际上要读取相同的数据就有问题,线程B只能从主内存中读,就可能读取的线程A加工前的数据。
2、线程A从主内存获取一个整型数据,对其做自增操作,线程B在线程A操作的同时对这个整型数据进行自增操作,假设读取的数据都是1,两个线程自增完成,在刷新到高速缓存中时,都是2,最后刷新回主内存的时候,也都是2,会存在少加的情况。
针对这两种情况,Java分别采用volatile和synchronized关键字进行处理。