专栏原创出处:github-源笔记文件 (opens new window) ,github-源码 (opens new window),欢迎 Star,转载请附上原文出处链接和本声明。
Java 并发编程专栏系列笔记,系统性学习可访问个人复盘笔记-技术博客 Java 并发编程 (opens new window)
# 什么是数据竞争
当程序未正确同步时,就会存在数据竞争。
java 内存模型规范对数据竞争的定义如下:在一个线程中写一个变量,在另一个线程读同一个变量,而且写和读没有通过同步来排序。
如果一个多线程程序能正确同步,这个程序将是一个没有数据竞争的程序,程序的执行将具有顺序一致性。
# 理论参考模型-顺序一致性内存模型
顺序一致性内存模型是一个被计算机科学家理想化了的理论参考模型,它为程序员提供了极强的内存可见性保证。JMM 在规范里也保证了顺序一致性。顺序一致性内存模型有两大特性:
- ① 一个线程中的所有操作必须按照程序的顺序来执行
- ② 所有线程都只能看到一个单一的操作执行顺序
=举例说明=:
假设有两个线程 A 和 B 并发执行(线程 A 执行后线程 B 执行)。其中
- A 线程有三个操作,它们在程序中的顺序是:A1->A2->A3。
- B 线程有三个操作,它们在程序中的顺序是:B1->B2->B3。
针对第 ① 特征说明:
- 线程 A 程序的执行顺序永远是 A1->A2->A3
- 线程 B 程序的执行顺序永远是 B1->B2->B3
针对第 ② 特征说明:
- 如果正确同步的话,线程 A 执行后释放监视器给线程 B 执行顺序将是 A1->A2->A3->B1->B2->B3。
- 如果未正确同步的话,可能(CPU 抢占问题)出现的顺序是 A1->A2->B1->A3->B2->B3。
未正确同步程序在顺序一致性模型中虽然整体执行顺序是无序的,但所有线程都只能看到一个一致的整体执行顺序。以上图为例,线程 A 和 B 看到的执行顺序都是:A1->A2->B1->A3->B2->B3。之所以能得到这个保证是因为顺序一致性内存模型中的每个操作必须立即对任意线程可见。
# JMM-顺序一致性内存模型
顺序一致性,保证程序的执行顺序一致,JMM 会根据一定规则(比如遵循 happens-before 原则),会对程序执行指令进行重排序,达到对编译器和处理器优化的目标。在 JMM 模型下,在不影响程序执行结果的前提下,编译器、处理器会对指令进行重排序。
特别说明:
假如线程 A 的 "A2" 操作必须保证对线程 B 的"B2"的可见性。每个线程临界区的代码重排序,可能最终执行顺序为:
A3->A2
->B3->B2
>B2->A1->B1
# 理论参考模型与 JMM 模型差异比对
实际上,【理论参考模型-顺序一致性内存模型】我们很少用。
- 因为这样编译器和处理器无法对程序做到优化,在 Java 中我们使用的是可以进行指令重排序的 JMM 模型。
- 顺序一致模型要求线程的每一个操作都具有原子性,也就是说,读写都会操作主存,这样的效率肯定会比 JMM 模型下先对线程本地内存操作的方式要低的多。