1 并发编程模型的两个关键问题
- 线程间如何通信?即:线程之间以何种机制来交换信息
- 线程间如何同步?即:线程以何种机制来控制不同线程间操作发⽣的相对顺序
- 有两种并发模型可以解决这两个问题:
消息传递并发模型
共享内存并发模型
- 这两种模型之间的区别如下表所示:
在Java中,使⽤的是共享内存并发模型。
2 Java内存模型的抽象结构
2.1 运⾏时内存的划分
- 对于每⼀个线程来说,栈都是私有的,⽽堆是共有的。
- 也就是说在栈中的变量(局部变量、⽅法定义参数、异常处理器参数)不会在线程之间共享,也就不会有内存可⻅性(下⽂会说到)的问题,也不受内存模型的影响。⽽在堆中的变量是共享的,本⽂称为共享变量。所以,内存可⻅性是针对的共享变量。
2.2 既然堆是共享的,为什么在堆中会有内存不可⻅问题?
- 这是因为现代计算机为了⾼效,往往会在⾼速缓存区中缓存共享变量,因为cpu访
问缓存区⽐访问内存要快得多。
线程之间的共享变量存在主内存中,每个线程都有⼀个私有的本地内存,存储了该线程以读、写共享变量的副本。本地内存是Java内存模型的⼀个抽象概念,并不真实存在。它涵盖了缓存、写缓冲区、寄存器等。
- Java线程之间的通信由Java内存模型(简称JMM)控制,从抽象的⻆度来说,JMM定义了线程和主内存之间的抽象关系。JMM的抽象示意图如图所示:
从图中可以看出:
- 所有的共享变量都存在主内存中。
- 每个线程都保存了⼀份该线程使⽤到的共享变量的副本。
- 如果线程A与线程B之间要通信的话,必须经历下⾯2个步骤:
- 线程A将本地内存A中更新过的共享变量刷新到主内存中去。
- 线程B到主内存中去读取线程A之前已经更新过的共享变量。
- 所以,线程A⽆法直接访问线程B的⼯作内存,线程间通信必须经过主内存。
- 注意,根据JMM的规定,线程对共享变量的所有操作都必须在⾃⼰的本地内存中进⾏,不能直接从主内存中读取。
- 所以线程B并不是直接去主内存中读取共享变量的值,⽽是先在本地内存B中找到这个共享变量,发现这个共享变量已经被更新了,然后本地内存B去主内存中读取这个共享变量的新值,并拷⻉到本地内存B中,最后线程B再读取本地内存B中的新值。
- 那么怎么知道这个共享变量的被其他线程更新了呢?
- 这就是JMM的功劳了,也是JMM存在的必要性之⼀。JMM通过控制主内存与每个线程的本地内存之间的交互,来提供内存可⻅性保证。
Java中的volatile关键字可以保证多线程操作共享变量的可⻅性以及禁⽌指令重排序,synchronized关键字不仅保证可⻅性,同时也保证了原⼦性(互斥性)。在更底层,JMM通过内存屏障来实现内存的可⻅性以及禁⽌重排序。
为了程序员的⽅便理解,提出了happens-before,它更加的简单易懂,从⽽避免了程序员为了理解内存可⻅性⽽去学习复杂的重排序规则以及这些规则的具体实现⽅法。
2.3 JMM与Java内存区域划分的区别与联系
上⾯分别提到了JMM和Java运⾏时内存区域的划分,这两者既有差别⼜有联系:
- 区别:
两者是不同的概念层次。JMM是抽象的,他是⽤来描述⼀组规则,通过这个规则来控制各个变量的访问⽅式,围绕原⼦性、有序性、可⻅性等展开的。⽽Java运⾏时内存的划分是具体的,是JVM运⾏Java程序时必要的内存划分。
- 联系
都存在私有数据区域和共享数据区域。⼀般来说,JMM中的主内存属于共享数据区域,他是包含了堆和⽅法区;同样,JMM中的本地内存属于私有数据区域,包含了程序计数器、本地⽅法栈、虚拟机栈。
实际上,他们表达的是同⼀种含义,这⾥不做区分。
了解
- lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。标识后会清空工作内存中此变量的值(如果此线程的工作内存中有此变量的话)。
- unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。释放前必须把工作内存中的变量同步到主内存中(这个是一定的)。
- read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用。
- load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
- assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
- write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。