本章主要讲了JMM内存模型原理及使用volatile、锁、concurrent并发包、final域等方式来进行多线程高并发环境下编码。
Java内存模型的基础
并发编程模型的两个关键问题
- 线程之间如何通信
- 共享内存
- 消息传递
- 线程之间如何同步
通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。
Java内存模型的抽象结构
JMM:Java内存模型。
两个步骤:
- 线程A把本地内存A中更新过的共享变量刷新到主内存中去。
- 线程B到主内存中去读取线程A之前已更新过的共享变量。
从源代码到指令序列的重排序
并发编程模型的分类
happens-before简介
重排序
重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。
数据依赖性
数据依赖性
as-if-serial语义
描述:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。
1 | double pi = 3.14; // A |
重排序对多线程的影响
1 | class ReorderExample { |
顺序一致性
- 数据竞争与顺序一致性
- 顺序一致性内存模型
- 同步程序的顺序一致性效果
- 未同步程序的执行特性
volatile的内存语义
volatile的特性
- 可见性
- 原子性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27class VolatileFeaturesExample {
volatile long vl = 0L; // 使用volatile声明64位的long型变量
public void set(long l) {
vl = l; // 单个volatile变量的写
}
public void getAndIncrement () {
vl++; // 复合(多个)volatile变量的读/写
}
public long get() {
return vl; // 单个volatile变量的读
}
}
==> 等价以下代码
class VolatileFeaturesExample {
long vl = 0L; // 64位的long型普通变量
public synchronized void set(long l) { // 对单个的普通变量的写用同一个锁同步
vl = l;
}
public void getAndIncrement () { // 普通方法调用
long temp = get(); // 调用已同步的读方法
temp += 1L; // 普通写操作
set(temp); // 调用已同步的写方法
}
public synchronized long get() { // 对单个的普通变量的读用同一个锁同步
return vl;
}
}
锁的内存语义
锁是Java并发编程中最重要的同步机制。
ReentrantLock
在ReentrantLock中,调用lock()方法获取锁;调用unlock()方法释放锁。
1 | class ReentrantLockExample { |
concurrent包
final域
inal域的重排序规则可以确保:在引用变量为任意线程可见之前,该引用变量指向的对象的final域已经在构造函数中被正确初始化过了。
1 | public class FinalReferenceEscapeExample { |
双重检查锁
1 | /** |