Skip to content

Commit c9b7485

Browse files
author
代码风水师
committed
更新了java内存模型及原子性、可见性、有序性的文章
1 parent 263745e commit c9b7485

9 files changed

Lines changed: 82 additions & 27 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
* [Java多线程与并发框 (第 03 篇) 深入理解:Java内存模型与原子性、可见性、有序性](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/JavaMemoryModle.md)
4141
* [Java多线程与并发框 (第 04 篇) 深入理解:重排序、屏障指令、as-if-serial、happens-before规则](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/ThreadRule.md)
4242
* [Java多线程与并发框 (第 05 篇) 深入理解:顺序一致性模型](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/SequentialConsistencyModel.md)
43-
* [Java多线程与并发框 (第 06 篇) 深入理解:volatile 关键字](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/volatile.md)
44-
* [Java多线程与并发框 (第 07 篇) 深入理解:synchronized 关键字](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/synchronized.md)
43+
* [Java多线程与并发框 (第 06 篇) 深入理解:synchronized 关键字](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/synchronized.md)
44+
* [Java多线程与并发框 (第 07 篇) 深入理解:volatile 关键字](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/volatile.md)
4545
* [Java多线程与并发框 (第 08 篇) 深入理解:Java各种锁与无锁](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/.md)
4646
* [Java多线程与并发框 (第 09 篇) 深入理解:CAS](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/CompareAndSwap.md)
4747
* [Java多线程与并发框 (第 10 篇) 深入理解:并发包的基石 -- 队列同步器 AQS](https://github.com/about-cloud/JavaCore/blob/master/resource/markdown/multithreads/AbstractQueuedSynchronizer.md)

resource/markdown/database/Mycat1.x.md

Whitespace-only changes.

resource/markdown/database/ShardingSphere3.x.md

Whitespace-only changes.

resource/markdown/database/Zebra2.9.x.md

Whitespace-only changes.

resource/markdown/multithreads/JavaMemoryModle.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
**主内存:** Java内存规定了所有变量都存储在主内存(Main Memory)中,各个线程又有自己的本地内存(工作内存),本地内存保存着主内存中部分变量。具体访问方式如下:
1010

11-
![JMM工作方式](https://i.loli.net/2018/12/18/5c188d882a730.png)
11+
![JMM工作方式](https://i.loli.net/2018/12/19/5c19a23c8ac3a.png)
1212

1313
**1、lock加锁:**为了保证访问主内存变量的线程安全性,在访问前一般会加锁处理;
1414

@@ -42,15 +42,38 @@ a++;
4242
double b = 1.5;
4343
```
4444

45-
45+
Java内存模型只保证单一的操作具有原子性,比如上面的 `int a = 1;` 是一个单子的操作,所以具有原子性。而 `a++` 操作在底层会分为三个操作:1)、读取a的值给临时变量;2)、临时变量a的值加1操作;3)、将加操作后的值赋值给a。每个操作都是原子的,但Java内存模型在多线程下并不能保证多操作具有整体原子性,因为它也不知道这个整体内有多少操作,用户想要达到多操作具有整体原子性,需要对响应的代码块做同步(synchronous)处理,比如使用 有锁的`synchronized` 或 无锁的`CAS`
4646

4747

4848

4949
#### 2、可见性(Visibility)
5050

51+
这里的可见性是内存可见性。
52+
53+
![多线程共访变量](https://i.loli.net/2018/12/19/5c19a77a8df9a.png)
54+
55+
如上图,线程1和线程2在未同步的情况下对共享内存(主内存)中的变量进行访问,比如两个线程的操作都是对变量a进行加1操作。假设线程1首先获取主内存中变量a的值,随后线程2又获取了主内存变量a的值,此时它们工作内存中a的值都是1,它们各自将a的值加1操作,然后assign至工作内存,工作内存中变量a的值都是2,然后两个线程又将值刷新到主内存,最后的结果是主内存中变量a的值是2。虽然整体对a的值加1操作做了两次操作,但由于线程间的操作是互相隔离的,默认情况下无法感知内存变量的值在随后的变化,也就无法访问内存中最新的变量值,这就是内存可行性的问题。
56+
57+
如何解决内存可见性的问题?
58+
59+
1)、对进入临界区的线程做同步处理(比如 synchronized),同一时刻仅有一个线程能够访问临界区的资源;
60+
61+
2)、使用 volatile 关键字保证内存可见性,它能保证访问临界区资源的所有线程总能看到共享资源的最新值;
5162

63+
3)、CAS无锁化。
5264

5365

5466

5567
#### 3、有序性(Ordering)
5668

69+
线程内的所有操作都是有序的,既程序执行的顺序按照代码的先后顺序执行。比如下面的示例:
70+
71+
```java
72+
int a = 1;
73+
int b = 2;
74+
int c = a + b;
75+
```
76+
77+
线程内程序会先执行 `int a = 1;` ,然后执行 `int b = 2;` 最后执行`int c = a + b;`
78+
79+
关于 **指令重排序****顺序一致性模型** 参见后面的章节。

resource/markdown/multithreads/SequentialConsistencyModel.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44

55
#### 一、竞态条件(Race Condition)
6-
> 计算的正确性取决于 `多个线程` 执行的 `时序` 时,就会发生 **静态条件**
6+
> 计算的正确性取决于 `多个线程` 执行的 `时序` 时,就会发生 **竞态条件**
77
88
#### 二、顺序一致性模型
99
> **对内存可见性的保证
Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
2+
13
<h3 style="padding-bottom:6px; padding-left:20px; color:#ffffff; background-color:#E74C3C;">一、重排序</h3>
24

3-
> 为了提升程序的执行性能,**编译器****处理器** 常常会对程序指令序列进行 **重排序**
5+
![排序](https://i.loli.net/2018/12/19/5c19f0bccee8e.jpeg)
6+
7+
前篇文章已经讲了Java内存模型和与其三个特性:原子性、可见性、有序性。但事实上,为了提升程序的执行性能,**编译器****处理器** 常常会对程序指令序列进行 **重排序**
48

59
##### 重排序分为以下几种:
610

@@ -13,35 +17,52 @@
1317

1418
<h3 style="padding-bottom:6px; padding-left:20px; color:#ffffff; background-color:#E74C3C;">二、屏障指令</h3>
1519

16-
> 内存屏障(Memory Barrier,或有时叫做内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则禁止重排序。
20+
![fence](https://i.loli.net/2018/12/19/5c19f01f8d3da.jpg)
21+
22+
> 内存屏障(Memory Barrier,或称为内存栅栏,Memory Fence)是一种CPU指令,用于控制特定条件下的重排序和内存可见性问题。Java编译器也会根据内存屏障的规则在一定程度地禁止重排序。
23+
24+
25+
26+
<h3 style="padding-bottom:6px; padding-left:20px; color:#ffffff; background-color:#E74C3C;">三、as-if-serial 语句</h3>
27+
28+
> 重排序也不能毫无规则,否则语义就变得不可读, **as-if-serial语句** 给重排序戴上紧箍咒,起到约束作用。
1729
30+
**as-if-serial语句** 规定重排序要满足以下两个规则:
1831

32+
- 在单线程环境下不能改变程序执行的结果;
33+
- 存在数据依赖关系代码(指令)片段的不允许重排序。
1934

20-
<h3 style="padding-bottom:6px; padding-left:20px; color:#ffffff; background-color:#E74C3C;">三、as-if-serial语句</h3>
35+
比如下面的代码:
2136

22-
> **as-if-serial语句** 给重排序戴上紧箍咒,起到约束作用。它规定不管怎么重排序,**(单线程)程序的执行结果不能被改变**。编译器、运行时和处理器都必须遵守as-if-serial语义。 为了遵守as-if-serial语义,编译器和处理器不会对存在数据依赖关系的操作做重排序。as-if-serial语义把单线程程序保护了起来,as-if-serial语义使单线程程序员无需担心重排序会干扰他们,也无需担心内存可见性问题。
37+
```java
38+
int a = 1; //
39+
int b = 2; //
40+
int c = a + b; // 依赖于 ① 和 ②
41+
return c;
42+
```
2343

24-
比如
44+
可能会被优化成
2545

2646
```java
27-
int a = 1;
28-
int b = 1 + a;
29-
int c = 2;
30-
int d = b + c;
31-
// 有可能优化成以下情况
32-
int a = 1;
33-
int c = 2;
34-
int b = 1 + a;
35-
int d = b + c;
47+
int b = 2; //
48+
int a = 1; //
49+
int c = a + b; // 依赖于 ① 和 ②
50+
return c;
3651
```
3752

53+
上述的重排序既没有改变单线程下程序运行的结果,又没有对存在依赖关系的指令进行重排序。
54+
55+
3856

57+
<h3 style="padding-bottom:6px; padding-left:20px; color:#ffffff; background-color:#E74C3C;">四、happens-before 规则</h3>
3958

40-
<h3 style="padding-bottom:6px; padding-left:20px; color:#ffffff; background-color:#E74C3C;">四、happens-before规则</h3>
59+
> 产生的背景是为了确保多线程操作下具有内存可见性。
60+
>
61+
> 如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。换句话说,操作1 happens-before 操作2,那么操作1的结果是对操作2可见的。
62+
>
63+
> 这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。
4164
42-
> 如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。**这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。**
4365

44-
![happens-before规则](https://upload-images.jianshu.io/upload_images/11476758-07e4c5ce8a9cf95b.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
4566

4667
#### 规则:
4768

@@ -50,4 +71,10 @@ int d = b + c;
5071
- **3. volatile变量规则**:对一个volatile域的写,happens-before 于任意后续对这个volatile域的读
5172
- **4. 传递性**:如果A happens-before B,且B happens-before C,那么A happens-before C
5273
- **5. start规则**:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作
53-
- **6. join规则**:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
74+
- **6. join规则**:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
75+
76+
77+
78+
### 总结:
79+
80+
**as-if-serial** 语句保证单线程环境下不能改变程序执行的结果,**happens-before** 规则保证多线程环境下不能改变程序执行的结果。

resource/markdown/multithreads/synchronized.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,14 @@
2727
2828
##### 2.1、对象加锁:
2929
> 使用 **monitorenter****monitorexit** 指令分别获取控制权和释放控制权。
30-
> ![monitor](https://upload-images.jianshu.io/upload_images/11476758-633049c76385f4d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
31-
> ![获取锁与释放锁](https://upload-images.jianshu.io/upload_images/11476758-9d8c25afef7a8ea6.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
30+
31+
![monitor](https://upload-images.jianshu.io/upload_images/11476758-633049c76385f4d1.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
32+
3233

3334
##### 2.1、方法加锁:
34-
> 方法级的同步是隐式,**即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中**。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 **ACC_SYNCHRONIZED** 访问标志区分一个方法是否同步方法。**当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是监视器一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor**。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。下面我们看看字节码层面如何实现:
35+
> 方法级的同步是隐式,**即无需通过字节码指令来控制的,它实现在方法调用和返回操作之中**。JVM可以从方法常量池中的方法表结构(method_info Structure) 中的 **ACC_SYNCHRONIZED** 访问标志区分一个方法是否同步方法。**当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有monitor(虚拟机规范中用的是监视器一词), 然后再执行方法,最后再方法完成(无论是正常完成还是非正常完成)时释放monitor**。在方法执行期间,执行线程持有了monitor,其他任何线程都无法再获得同一个monitor。如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放。
36+
37+
3538

3639
---
3740
#### 四、JVM对synchronized的优化
@@ -54,6 +57,8 @@
5457
##### 4.5、`★★★★★`锁消除
5558
> 消除锁是虚拟机另外一种锁的优化,这种优化更彻底,Java虚拟机在JIT编译时(可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间,如下StringBuffer的append是一个同步方法,但是在add方法中的StringBuffer属于一个局部变量,并且不会被其他线程所使用,因此StringBuffer不可能存在共享资源竞争的情景,JVM会自动将其锁消除。
5659
60+
61+
5762
---
5863
#### 五、synchronized关键点
5964
> 中断与synchronized:

resource/markdown/multithreads/volatile.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
> 把对 **volatile变量**的单个读/写,看成是使用 **同一个监视器锁** 对这些单个读/写操作做了 **同步**
77
> 原理:插入内存屏蔽指令,禁止一定条件下的重排序。
88
* **volatile** 是轻量级的同步机制
9-
![Java内存模型](https://upload-images.jianshu.io/upload_images/11476758-e9dfc21dc22a3e1f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
9+
1010

1111

1212
* 举例说明:

0 commit comments

Comments
 (0)