- JMM
Java内存模型(Java Memory Model )是一种规范,规定了线程如何正确的从内存中读取数据。
java内存模型是共享内存的并发模型,线程之间主要通过读-写共享变量来完成隐式通信。(通信是指线程之间以何种机制来交换信息,主要有两种:共享内存和消息传递)
每个java线程拥有独立的工作内存,多个线程共享主内存,当共享数据发生变化时,A线程修改了共享数据“s”,而B线程继续从自己的工作内存取出“s”,此时“s”数据已过期。JMM规范则定义了对于共享数据如何正确读取的机制,保证程序执行的正确性。
为了使处理器内部的运算单元能够尽量的被充分利用,处理器可能会对代码进行重新排序,这就是处理器优化。同时,Java虚拟机的即时编译器(JIT)也会做指令重排序以优化程序执行效率。
我们来看一下,JMM规范的提出是为了解决哪些问题
1)可见性问题(缓存不一致问题),A线程对数据的修改,对B不可见
2)处理器和编译器重排序时,程序执行结果可能不一致
3)一个操作可能部分成功,部分失败,即操作的原子性问题
规范有了,那么,这些问题是怎么解决的呢?
要保证并发程序的正确性,需要满足以下3个特性:
1)原子性(操作要么都成功,要么都失败)
2)可见性
3)有序性
其实满足了这3个特性,也就解决了上面提到了3个问题。下面来看一下Java是如何解决这3个问题的。
- volatile和sychronized关键字
原子性:在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter和monitorexit。这两个字节码,在Java中对应的关键字就是synchronized。
可见性:Java中的volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次是用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。
有序性:
1)JMM中happens-before原则进行保证(对编译器和处理器重排序的约束原则)
2)volatile关键字会禁止指令重排。synchronized关键字保证同一时刻只允许一条线程操作(加锁)。
volatile boolean init; // 共享变量
//-------Thread-1----------
obj = createObj();
init = true;
//-------Thread-2----------
while (!init) {
sleep();
}
useTheObj(obj);
//-------------------------
volatile
在上面代码的作用:
多个线程访问共享变量,volatile关键字保证了后面的指令不会放到屏障的前面,其前面的指令也不会放到屏障后面,保证obj创建完成,“init”变量才会=true,即指令init = true;
不会被重排序到obj = createObj();
之前,从而保证Thread-2使用useTheObj()
时obj已创建完成。
同时保证了init的可见性,init一旦被修改会立即写入到主存,从而使其他持有该变量的缓存失效。
但volatile不保证原子性。
- Java内存区域
栈:线程私有,包含本地方法栈、jvm虚拟机栈、程序计数器
堆:线程共享,包含堆区、方法区、直接内存(jvm以外的机器内存)
并发之原子性、可见性、有序性
别再问什么是Java内存模型了,看这里!
java内存模型以及happens-before规则
让你彻底理解Synchronized