并发编程模型的分类

线程之间如何通信和如何保持同步是是两个关键的问题。
先解决线程之间通信的问题。线程之间的通信机制有两种:第一种是共享内存;第二种是消息传递。在共享内存中,线程之间通过"读-写"线程中的公共区域来隐式地通信。消息传递机制下,线程之间没有公共区域,需要显式进行通信。
接着说下同步。在共享内存机制下,线程的同步需要显式地指定代码块或方法来实现;在消息传递机制下,由于消息的发生在消息的接收之前,已经隐式地实现了同步。

java内存模型抽象

在java中,实例域和静态域和数组元素都放在堆内存,堆内存在线程之间共享。局部变量和方法参数和异常处理器参数不会在线程之间共享。Java之间的通信由java内存模型控制(Java Memory Model简称JMM),JMM决定一个线程对变量的写入何时对另一个线程可见。线程之间的共享变量存储在主内存中,每一个线程有自己的本地内存。本地内存是 JMM的抽象,实际上并不存在。

Java共享内核架构 java 共享内存的实现_Java共享内核架构

线程通信流程:

  1. 线程A将本地内存变量刷新到主内存中
  2. 线程B到主内存去读取线程A刷新到主内存的变量

数据竞争

当程序未正确同步时,就会存在数据竞争。java内存模型规范对数据竞争的定义

  1. 在一个线程写变量
  2. 在另一个线程读这个变量
  3. 这两个线程没有同步
    当出现数据竞争时,容易出现一些违反直觉的结果。

顺序一致性

不管程序是否同步,所有线程都只能看到一个单一的操作执行顺序。在顺序一致性内存模型中,每个操作都必须原子执行且立刻对所有线程可见。

Java共享内核架构 java 共享内存的实现_执行顺序_02

在概念上,顺序一致性模型有一个单一的全局内存,这个内存通过一个左右摆动的开关可以连接到任意一个线程。同时,每一个线程必须按程序的顺序来执行内存读 / 写操作。从上图我们可以看出,在任意时间点最多只能有一个线程可以连接到内存。当多个线程并发执行时,图中的开关装置能把所有线程的所有内存读 / 写操作串行化。

假设有两个线程 A 和 B 并发执行。其中 A 线程有三个操作,它们在程序中的顺序是:A1->A2->A3。B 线程也有三个操作,它们在程序中的顺序是:B1->B2->B3。
假设这两个线程使用监视器来正确同步:A 线程的三个操作执行后释放监视器,随后 B 线程获取同一个监视器。那么程序在顺序一致性模型中的执行效果将如下图所示:

Java共享内核架构 java 共享内存的实现_消息传递_03

现在我们再假设这两个线程没有做同步,下面是这个未同步程序在顺序一致性模型中的执行示意图:

Java共享内核架构 java 共享内存的实现_共享内存_04

无论线程有没有同步,所有线程看到的操作执行的顺序都是一样的。但是在JMM中没有这方面的保证,不但整体的执行顺序是无序的,不同线程看到的操作执行顺序还是不同的,可能A线程看到的是B1-B2-B3-A1-A2-A3;B线程看到的是A1-A3-B1-B2-B3-A2.