参考URL:Java并发性和多线程介绍
1.堆栈存放:
(1)一个本地变量可能是原始类型,在这种情况下,它总是“呆在”线程栈上;
(2)一个本地变量也可能是指向一个对象的一个引用。在这种情况下,引用(这个本地变量)存放在线程栈上,但是对象本身存放在堆上。
(3)一个对象的成员变量可能随着这个对象自身存放在堆上。不管这个成员变量是原始类型还是引用类型。
(4)静态成员变量跟随着类定义一起也存放在堆上。
总结:本地变量原始类型都在栈上;成员变量可能存在在堆上,不管是原始类型还是引用类型。
2.Java内存模型
如果有两个线程同时执行MyRunnable中的run()方法,就会出现上面JVM内存模型图中所示的情景。
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 member2 = 67890;
}
public class MyRunnable implements Runnable(){
public void run() {
methodOne();
}
public void methodOne() {
int localVariable1 = 45;
MySharedObject localVariable2 =
MySharedObject.sharedInstance;
methodTwo();
}
public void methodTwo() {
Integer localVariable = new Integer(99);
}
}
(1)run() 调用methodOne(),methodOne() 调用methodTwo()。
(2)methodOne() 声明了一个原始类型的本地变量和一个引用类型的本地变量。
(3)每个线程执行methodOne() 都会在它们对应的线程栈上创建localVariable1 和localVariable2 的拷贝。localVariable1 拷贝的是它的副本,是线程安全的;localVariable2 拷贝的是它的对象引用,不一定线程安全的。
(4)然而,localVariable2的不同拷贝都指向堆上的同一个对象。代码中static final修饰的sharedInstance静态变量永远只有一个,它保存在堆上。localVariable2的两份拷贝都指向由MySharedObject指向的静态变量的同一个实例,MySharedObject实例也存放在堆上,对应图中Object3。
(5)MySharedObject中包含了两个原始类型成员变量、两个引用类型成员变量。注意,成员变量跟随其对象MySharedObject存放在堆上,两个long型原始类型成员变量也存放在堆上。两个引用类型成员变量指向另外两个Integer对象,对应图中Object2和Obejct4。
(6)methodTwo() 创建了localVariable 的本地变量,指向一个新的Integer实例,两个线程执行则存放两个不同的拷贝,对应图中Object1和Object5。
3。硬件内存架构:
CPU寄存器 -> CPU缓存 -> 内存。
4.Java内存模型和硬件内存架构之间的桥接
对应关系如下图,即堆栈可以存放在 寄存器/缓存/内存 任意的区域。
使用volatile关键字,保证直接从主存中读取共享变量,并且每次都会写入到主存中去。
使用同步语句块,一个同步块可以保证在同一时刻仅有一个线程可以进入代码的临界区。同步块还可以保证代码块中所有被访问的变量将会从主存中读入,当线程退出同步代码块时,所有被更新的变量都会被刷新回主存中去,不管这个变量是否被声明为volatile。
小知识点:可以访问一个类即可以访问这个类的成员变量