2-1 Java内存模型的基础
2-1-1 线程之间如何通信(内存模型分类)?
- 线程间的通信有两种:共享内存和消息传递
2-1-2 线程之间如何通信?
- 共享内存的并发模型里,线程之间通过对共享变量的写-读进行隐式地通信。
- 消息传递的并发模型里,线程之间必须通过发送消息进行显式地通信。
2-1-3 线程之间如何同步?
- 同步:程序中用于控制不同线程相对执行顺序的机制
- 共享内存的并发模型里,线程间同步必须是开发显式地自己定义某个方法或者那块代码段需要在线程间互斥执行。
- 消息传递的并发模型里,由于消息的发送必须在消息的接收前,所以同步是隐式的。
2-1-4 java采用的是共享内存的内存模型
- 每个线程都会去主存中读取共享变量缓存到本地内存中
- 线程A和B要通信 : 线程A把本地内存A中更新过的共享变量刷新回主存 ; 线程B从主存中读取线程A之前更新过的共享变量。
- 本质上看,Java内存模型通过控制主内存和每个线程之间本地内存的交互,来为程序员保证内存可见性的.
2-1-5 happens-before
happens-before用来阐述操作的可见性的重要规则。
- 程序顺序规则:在一个线程中每个操作,happens-before与他的后续操作
- 监视器规则:对一个锁的解锁,happens-before于对它的加锁
- volatile变量规则:对volatile变量的写,happens-before于后续对它的读
- 传递性:如果A happens-before B, B happens-before C ,那么 A happens-before C
2-2 volatile域内存语义
volatile申明的变量,当对其写的时候会立即刷新到主内存中,后续读此变量的线程会直接从主内存中读,而不是拿线程本地内存中的值。
- volatile写的内存语义 :对volatile变量的写实际上是通过改变主内存共享变量的值,向接下来读这个volatile变量的线程发送消息
- volatile读的内存语义 :volatile变量的读实际上是通过读取主内存共享变量,接收之前某个线程发出的修改消息。
- volatile读写的内存语义: 线程A写一个volatile变量,随后线程B读取这个volatile变量,实质上是线程A通过主内存向线程B发送了一个消息。
- 注意:volatile只能保证读写的原子性,不能保证运算操作(如++这种)的原子性,可以用CAS更新volatile保证线程安全(volatile+CAS),
2-3. 锁的内存语义
同步是指一个线程要等它的上一个线程执行完才能执行,同步机制包括volatile读写,CAS ,锁,这些可以实现线程间通信。锁是java并发编程里最重要的同步机制。锁除了让临界区互斥的执行,还可以让释放锁的线程向获取锁的线程发消息(通过修改主内存中共享变量)。
- 释放锁的内存语义:线程A释放锁,JMM把线程A本地内存中修改的共享变量刷新回主存。
- 获取锁的内存语义:线程B获取锁,JMM把线程B本地内存中的共享变量设置为无效,然后读取主内存中的共享变量,然后缓存到本地。
释放获取锁的内存语义:线程A释放锁,然后线程B获取锁,实质上是线程A通过修改主内存中共享变量的方式,向线程B发送消息。
concurrent包的实现示意图:
2-4. final域的内存语义
不可变
2-5. 双重检查锁定和延迟加载
2-5-1 传统的线程安全的双重检查锁定的懒加载初始化单例
public class SafeLazyInitialization {
private static Object instance;
public static Object getInstance(){
if(instance==null){
synchronized (SafeLazyInitialization.class){
if(instance==null){
instance=new Object();
}
}
}
return instance;
}
}
2-5-2 传统的双重检查锁定有缺陷
当第一个进入临界区的线程初始化单例对象时
instance=new Object();
会初始化对象,此分为3部分:
memory=allocate();//1:分配内存
ctorInstance(memory);//2:初始化对象
instance=memory;3:将对象的引用指向改内存空间
上述2,3两步之间没有依赖关系,可能会被重排序:
memory=allocate();//1:分配内存
instance=memory;3:将对象的引用指向改内存空间(注意此时对象还没有初始化)
ctorInstance(memory);//2:初始化对象
如果初始化的线程A执行到第3步(instance被赋值不为空了),同时有另外的线程B进getInstance方法判断instance是否为空,不为空直接返回,这时候的线程B返回的instance还没有初始化。
2-5-3 解决方案
2-5-3-1 禁止new对象的2,3两步重排序.
基于volatile的方案:
/**
* 单例:线程安全的双重检查锁定的懒加载初始化
* Created by TJP on 2016/11/12.
*/
public class SafeLazyInitialization {
private volatile static Object instance;
public static Object getInstance() {
if (instance == null) {
synchronized (SafeLazyInitialization.class) {
if (instance == null) {
instance = new Object();
}
}
}
return instance;
}
}
volatile实际上是禁止,实例对象时先设置对象的引用,然后在初始化对象。
2-5-3-2 允许2,3两步的重排序,但不让其他线程看得到。
基于类初始化的方案:
/**
* 单例:线程安全的静态内部类的懒加载初始化
* Created by TJP on 2016/11/12.
*/
public class InstanceFactory {
private static class InstanceHolder{
private static Object instance=new Object();
}
public static Object getInstance(){
return InstanceHolder.instance;//线程安全的类初始化
}
}
类的初始化时线程安全的,只能有一个线程初始化,其他线程等待,这样其他线程没法看见2,3两步的重排序。