第3章-Java内存模型

本章主要讲了JMM内存模型原理及使用volatile、锁、concurrent并发包、final域等方式来进行多线程高并发环境下编码。

Java内存模型的基础

并发编程模型的两个关键问题

  • 线程之间如何通信
    • 共享内存
    • 消息传递
  • 线程之间如何同步
    通信是指线程之间以何种机制来交换信息。在命令式编程中,线程之间的通信机制有两种:共享内存和消息传递。

Java内存模型的抽象结构

JMM:Java内存模型。
两个步骤:

  • 线程A把本地内存A中更新过的共享变量刷新到主内存中去。
  • 线程B到主内存中去读取线程A之前已更新过的共享变量。
    Alt text

从源代码到指令序列的重排序

Alt text

并发编程模型的分类

Alt text

happens-before简介

Alt text

重排序

重排序是指编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

数据依赖性

数据依赖性

Alt text

as-if-serial语义

描述:不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译器、runtime和处理器都必须遵守as-if-serial语义。

1
2
3
double pi = 3.14; // A
double r = 1.0; // B
double area = pi * r * r; // C

Alt text

重排序对多线程的影响

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ReorderExample {
int a = 0;
boolean flag = false;
public void writer() {
a = 1; // 1
flag = true; // 2
}
Public void reader() {
if (f?lag) { // 3
int i = a * a; // 4
……
}
}
}

Alt text

顺序一致性

  • 数据竞争与顺序一致性
  • 顺序一致性内存模型
  • 同步程序的顺序一致性效果
  • 未同步程序的执行特性

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
    27
    class 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并发编程中最重要的同步机制。
Alt text

ReentrantLock

在ReentrantLock中,调用lock()方法获取锁;调用unlock()方法释放锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ReentrantLockExample {
int a = 0;
ReentrantLock lock = new ReentrantLock();
public void writer() {
lock.lock();     // 获取锁
try {
a++;
} f  inally {
lock.unlock();  // 释放锁
}
}
public void reader () {
lock.lock();     // 获取锁
try {
int i = a;
……
} f  inally {
lock.unlock();  // 释放锁
}
}
}

concurrent包

Alt text

final域

inal域的重排序规则可以确保:在引用变量为任意线程可见之前,该引用变量指向的对象的final域已经在构造函数中被正确初始化过了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class FinalReferenceEscapeExample {
final int i;
static FinalReferenceEscapeExample obj;
public FinalReferenceEscapeExample () {
i = 1; // 1写final域
obj = this; // 2 this引用在此"逸出"
}
public static void writer() {
new FinalReferenceEscapeExample ();
}
public static void reader() {
if (obj != null) { // 3
int temp = obj.i; // 4
}
}
}

双重检查锁

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
27
28
29
/**
* @desc DCL单例 + volatile(禁止指令重排)
* @Author xw
* @Date 2019/8/20
*/
public class SingletonDemo {
private static volatile SingletonDemo instance = null;
private SingletonDemo() {
System.out.println(Thread.currentThread().getName() + "\t 我是构造方法");
}
public static SingletonDemo getInstance() {
if (instance == null) {
synchronized (SingletonDemo.class) {
if (instance == null) {
instance = new SingletonDemo();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 1; i < 100; i++) {
new Thread(() -> {
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
SingletonDemo.getInstance();
}, "T" + i).start();
}
}
}