JMM
在 java 中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享。 JMM 定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读 / 写共享变量的副本。本地内存是 JMM 的一个抽象概念,并不客观存在。
线程栈到底放了什么?
- 线程栈包含正在执行的每个方法的所有局部变量(调用堆栈上的所有方法), 基本类型的所有局部变量(boolean,byte,short,char,int,long,float,double)完全存储在线程堆栈。
- 堆包含了在Java应用程序中创建的所有对象,无论创建该对象的线程是什么。 这包括基本类型的包装类(例如Byte,Integer,Long等)。 无论是创建对象并将其分配给局部变量,还是创建为另一个对象的成员变量,该对象仍然存储在堆上。
- 局部变量可以是基本类型,在这种情况下,它完全保留在线程堆栈上。
- 局部变量也可以是对象的引用。 在这种情况下,引用(局部变量)存储在线程堆栈中,但是对象本身存储在堆(Heap)上。
- 对象的成员变量与对象本身一起存储在堆上。 当成员变量是基本类型时,以及它是对象的引用时都是如此。
- 静态类变量也与类定义一起存储在堆上。
并发问题三要素
可见性
可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到。
原子性
一组操作要么全部执行,要么都不执行,中途不会被中断。
有序性
程序执行的顺序按照代码书写的顺序执行。
Volitile只保障了可见性和有序性,并不能保障原子性。举一个很出名的例子说明下:
public class SingleInstance {
private volatile static SingleInstance instance;
public static SingleInstance getInstance() { //1
if (instance == null) { //2
synchronized(SingleInstance.class) { //3
if(instance == null) { //4
instance = new SingleInstance(); //5
}
}
}
return instance; //6
}
}
上例中的第5个步骤的代码, new SingleInstance()并不是原子性的, 在jvm中被拆分为三个步骤:
a. memory = allocate() //分配内存
b. ctorInstanc(memory) //初始化对象
c. instance = memory //设置instance指向刚分配的地址
如果不加volatile字段(保障有序和可见性), 在多线程的并发下,可能出现这样一种情况, 比如A线程执行到第5步, 然后由于jvm指令重排序,abc三个步骤中执行了a和c, 此时B线程执行到了第2步,判断instance != null(A线程执行了c),导致B线程返回未初始化的实例,使得程序异常。