JMM
Java内存模型的抽象示意图:
Java线程间消息通信:
线程A将x=1传递给线程B,经历过程如下:
- 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
- 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
至于如何保证线程间数据的准确性,则需要利用同步锁来配合。
重排序
从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:
重排序重点理解以下原则:
- 数据依赖性原则
- as-if-serial语义
- happens-before
- 控制依赖关系
- 顺序一致性
重排序带来的问题就是程序执行结果会被改变。因此需要各种约定、规范来限制部分编译器、处理器重排序。
数据依赖性原则:对于两个操作A、B存在依赖关系的,编译器、处理器不会改变其执行顺序。
as-if-serial语义:对于单线程程序,无论怎么重排序,程序结果不能改变。
happens-before:前一操作结果对后一操作可见;前一操作按顺序排在后一操作前。
控制依赖关系:if、while等控制依赖,多线程中依旧会重排序。
顺序一致性:理想的内存模型,线程内操作必须有序。
锁
锁是java并发编程中最重要的同步机制。锁除了让临界区互斥执行外,还可以让释放锁的线程向获取同一个锁的线程发送消息。下面是锁释放-获取的示例代码:
class MonitorExample {
int a = 0;
public synchronized void writer() { //1
a++; //2
} //3
public synchronized void reader() { //4
int i = a; //5
……
} //6
}
下面对锁释放和锁获取的内存语义做个总结:
- 线程A释放一个锁,实质上是线程A向接下来将要获取这个锁的某个线程发出了(线程A对共享变量所做修改的)消息。
- 线程B获取一个锁,实质上是线程B接收了之前某个线程发出的(在释放这个锁之前对共享变量所做修改的)消息。
- 线程A释放锁,随后线程B获取这个锁,这个过程实质上是线程A通过主内存向线程B发送消息。
final域
写final域的重排序规则
写final域的重排序规则可以确保:在对象引用为任意线程可见之前,对象的final域已经被正确初始化过了。
读final域的重排序规则
读final域的重排序规则可以确保:在读一个对象的final域之前,一定会先读包含这个final域的对象的引用。