3.1.1并发模型中的两个关键问题
线程之间如何通信<br>(通信是指线程之间以何种机制来交换信息)
共享内存
在共享内存的并发模型里,<br>线程之间共享程序的公共状态,<br>通过写-读内存中的公共状态进行隐式通信。
消息传递
在消息传递的并发模型里,<br>线程之间没有公共状态,<br>线程之间必须通过发送消息显示进行通信
线程时间如何同步<br>(同步是指程序中用于控制不同线程间操作<br>发生的相对顺序的机制)
共享内存
同步是显示进行,<br>程序员必须显示指定某个方法<br>或者某段代码需要再线程之间<br>互斥执行。
消息传递
由于消息的发送必须在消息的接收之前<br>因此同步时隐式进行的。
java采用的是共享内存模型,<br>java线程时间的通信总是隐式进行的,<br>整个通信过程对于程序员完全透明。<br>
3.1.2java内存模型的抽象结构
共享变量(堆内存中)
java实例域
静态域
数组元素
非共享变量(线程独享)
局部变量
方法定义参数
异常处理器参数
线程和主存之间的抽象关系
线程之间的共享变量存储在主内存中(Main Memory),<br>每个线程都有一个私有的本地内存(Local Memory),<br>本地内存中存储了该线程以读/写共享变量的副本。<br>本地内存是JMM的一个抽象概念,并不真实存在。<br>他涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
JMM内存模型抽象结构示意图
JMM
java线程之间的通信有java内存模型(JMM)控制,<br>JMM决定一个线程对共享变量的写入何时对另一个线程可见
JMM通过控制主内存和每个线程的本地内存之间的交互,<br>来为程序员提供内存可见性的保证。
线程A和线程B通信
步骤一:<br>线程A把本地内存A中更新过的共享变量刷新到主内存中去。
步骤二:<br>线程B到主内存中去读取线程A之前一斤刚更新过的共享变量
3.1.3从源代码到指令的重个排序<br>(编译器和处理器常常对指令进行重排序,分为三种)
1、编译器优化重排序,<br>编译器在不改变单线程程序语义的前提下,<br>可以重新安排语句的执行顺序。
2、指令级并行的重排序<br>指令集并行技术(Instruction-Level-Parallelism,ILP),<br>将多条指令重叠执行。若不存在数据依赖性,<br>处理器可以改变语句对应机器指令的执行顺序。
3、内存系统的重排序<br>由于处理器使用缓存和读写缓冲区,<br>这使得加载和存储操作看上去可能在乱序执行
源代码到最终执行的指令序列示意图
3.1.4并发编程模型的分类
处理器使用写缓冲区临时保存向内存写入的数据,<br>写缓冲区可以保证指令流水线持续运行,<br>他可以避免由于处理器停顿下来等待向内存写入数据而产生的延迟。<br>同时,通过以批处理的方式刷新写缓存,<br>以及合并写缓冲区中对同一个内存地址的多次写,<br>较少对内存总线的占用。<br>但由于每个处理器上的写缓冲区,仅对他所在的处理器可见。<br>他会对内存操作的执行顺序产生重要的影响:<br>处理器对内存的读写操作的执行顺序,不一定域内存实际发生的读写操作顺序一致。
线程A:a=1; // A1 x=b;//A2<br>线程B:b=2; //B1 y=a;//B2<br>初始状态:a=b=0;<br>处理器允许执行后的结果为:x=y=0
处理器和内存的交互
处理器的重排序规则
常见的处理器都允许Store-Load重排序<br>常见的处理器都不允许对存在数据依赖的操作做重排序<br>sparc-TSO(Total Store Order)和X86拥有相对较强的内存模型,它们仅允许写读操作的重排序,<br>(因为他们都使用了写缓冲区)
<b>内存屏障的分类</b>(为了保证内存可见性,<br>java编译器在生成至指令序列的适当位置<br>会插入内存屏障指令<br>来禁止特定类型的额处理器重排序)
LoadLoad Barriers
Load1;LoadLoad;Load2<br>确保Load1数据的装载先于Load2<br>及所有后续装载指令的装载
StoreStore Barriers
Store1;StoreStore;Store2<br>确保Store1数据对其他处理器可见<br>(刷新到内存)先于Store2<br>及后续所有存储指令的存储
LoadStore Barriers
Load1;LoadStore;Store2<br>确保Load1数据装载先于Store2<br>及所有后续存储指令刷新到内存
StoreLoad Barriers
Store1;StoreLoad;Load2<br>确保Store1数据对其他处理器变得可见<br>(指刷新到内存)<br>先于Load2及后续装载指令的装载,<br>StoreLoad Barriers 会使该屏障之前的所有内存访问指令<br>(存储和装载指令)完成后,才执行该屏障之后的内存访问指令
是一个“全能型”的屏障,<br>他同时具有其他三个屏障的效果,<br>(处理器大多支持该屏障,<br>其他类型的屏障不一定被所有处理器支持)<br>该屏障开销会很昂贵,<br>因为当前处理器通常要把写缓冲区中国的数据全部刷新到内存中<br>(Buffer Fully Flush)
3.1.5happens-before简介
内容
从jdk5开始,java使用新的JSR-133内存模型(除非特别说明,<br>后续都是针对JSR-133内存模型),JSR-133使用happens-before的概念<br>来阐述操作之间的内存可见性。<br>在JMM中,如果一个操作执行的结果需要对另个一操作可见,<br>那么这两个操作之间必须要存在happens-before关系。<br>这里提到的两个操作,既可以在一个线程内,也可以在不同的线程之间。
规则
程序顺序规则:<br>一个线程的每个操作,happens-before于该线程中的任意后续操作
监视器锁规则:<br>对于一个锁的解锁,happens-before于随后对这个锁的加锁<br>
volatile变量规则:<br>对于一个volatile域的写,happens-before于任意后续对这个volatile域的读
传递性:<br>如果A happens-before B,<br>且B happens-before C,<br>那么A happens-before C
注意:
两个操作之间具有happens-before关系,<br>并不意味着前一个操作必须要在后一个操作之前执行!<br>happens-before仅仅要求前一个操作(执行结果)对后一个操作可见,<br>且前一个操作按顺序排在第二个操作之前(the first vivisible to and ordered before the second)。<br>
<b><font color="#c41230">happens-before与JMM关系图:<br>一个happens-before规则对应于一个或者多个编译器和处理器重排序规则</font></b>