1,内存模型
左侧绿色区域为共享内存区,右侧黄色区域为线程私有内存区
①方法区:存放被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码,方法区有一个运行时常量池存放编译器生成的字面量和符号引用。
②堆:虚拟机启动时创建,Java虚拟机管理内存中最大的一块,用于存放对象实例(注意:因为堆是垃圾收集器管理的主要区域,因此也叫GC堆)
③程序计数器:记录当前线程执行的字节码行号指示器,控制分支,循环,跳转,异常,线程恢复等基础功能
④虚拟机栈:虚拟机私有,线程创建时创建,代表java方法执行的内存模型。每个方法执行都会创建一个栈帧来存储方法的变量,操作数栈,动态链接方法,返回值,返回地址等信息。每个方法从调用到结束就对应于虚拟机栈中入栈和出栈的过程。
⑤本地方法栈:与虚拟机用到的Native方法相关。
2,内存执行模型
java程序的运行实体是线程,每个线程都会有一个工作内存(即栈空间)由线程私有,而内存模型归档所有的变量都存储在所有线程共享的堆内存,所以线程对变量的操作要:
①从堆内存中拷贝变量的副本到本线程的工作内存中
②在本线程的工作内存中操作变量副本
③操作完成后将变量写会回主内存
注意:如果方法中有一个局部变量是基本数据类型(如int, long, boolean, byte, short, char, float, double)则该变量直接存储在线程私有的栈内存中,如果本地变量是引用类型,则该引用存储在栈内存中,引用指向的实例存放在堆内存中。
3,CPU与内存的交互模型
读写速度CPU寄存器>CPU缓存>RAM
4,java线程与内核线程
内核线程是操作系统内核支持的线程,由操作系统内核完成线程管理,对于应用程序而言,一般不会直接调用内核线程,而是使用一种轻量级的进程(即通称意义上的线程),每个轻量级的进程都会映射到一个内核线程上,然后由操作系统将内核线程分发给各个处理器处理。
一个轻量级进程(线程)会映射到一个内核线程
5,线程安全问题
1)多个线程同时操作堆内存中的数据(线程间共享)时,可能引发线程安全问题,原因是:每个线程都会拷贝一份数据副本到线程私有内存区,如果线程A,B同一时间段内读取数据,修改,并回写,可能发生①A回写前,B就复制副本;②A回写后,B才复制副本等不确定问题。
2)程序运算的时候并不是所有操作在操作系统层都是原子性的,例如程序执行c = a+b,在指令层面可能会有:①load a to register1②load b to register2③add register1 register2 to register3④load register3 to c。这样的情况如果在多线程同时处理c变量的时候就有可能出现线程安全问题。另外:编译器语句重排;执行并行重排;内存系统重排等。
3)线程A修改了共享变量a的值,还未回写到共享存储区之前线程B是不知道修改后的值的,但在单线程下,修改后的值都是可见的,这叫可见性
4)java程序中,在本线程内所有的操作都视为有序的,这叫有序性;多线程环境下,一个线程中观察另外一个线程,所有操作都是无序的(由于指令重排/主内存和工作内存的延迟同步现象)
6,如何解决上面的原子性,可见性,有序性
①synchronized或ReentrantLock保证操作的原子性(volatile不能保证原子性)
②synchronized或volatile关键字保证可见性
③volatile保证有序性,因为它禁止重排优化
④happens-before原则
7,volatile的作用
①保证被volatile修饰的共享变量对所有的线程都是可见的,即:某个线程修改后的新值总是被其他线程立即得到
②禁止指令重排
③volatile不能保证原子性,例如下面的程序可能会有线程安全问题:
public class VolatileVisibility{
public volatile static int I = 0;
public static void increase(){
i++;//改操作不是原子性的,所以任然可能会有线程安全问题
}
}
修改方法是在increase方法上用synchronized修饰,属性定义上可去掉volatile修饰。
volatile保证可见性的方式:当修改一个volatile变量时,会强制将工作内存中改变后的值刷新到主内存;读取volatile变量时,会先将本线程工作内存中的值置为无效,重新从主内存里读取新副本。
④典型单例模式中禁止重排例子
Public class DoubleCheckLock {
private static DoubleCheckLock instance;
private DoubleCheckLock (){};
public DoubleCheckLock getInstance(){
if(null == instance){
synchronized (DoubleCheckLock.class){
if(null == instance){
instance = new DoubleCheckLock();
}
}
}
return instance;
}
}
上面的程序有线程安全问题:当线程B读取到instance不为空的时候,其他线程初始化的动作可能还没有完成,因为new DoubleCheckLock();大致可以分为如下3步:
①memory = allocate();//分配内存空间
②instance(memory);//初始化内存区域
③instance=memory;//将初始化好的内存区域赋值给instance
上面三步中的②③可能会出现重排,即先赋值,再初始化。因此此时应该在私有变量instance上用volatile修饰:
public volatile static DoubleCheckLock instance;