1.内存模型java
java中,全部实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享。局部变量,方法参数和异常参数不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。数组
Java线程之间的通讯由Java内存模型控制,内存模型决定一个线程对共享变量的写入什么时候对另外一个线程可见。内存模型定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每一个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。本地内存是内存模型的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其余的硬件和编译器优化。缓存
若是AB两个线程之间须要相互通讯,多线程
首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。并发
而后,线程B到主内存中去读取线程A以前已更新过的共享变量。性能
内存模型经过控制主内存与每一个线程的本地内存之间的交互,来提供内存可见性保证。优化
2.重排序spa
在执行程序时为了提升性能,编译器和处理器经常会对指令作重排序。线程
编译器优化的重排序。编译器在不改变单线程程序语义的前提下,能够从新安排语句的执行顺序。排序
指令级并行的重排序。现代处理器采用了指令级并行技术来将多条指令重叠执行。若是不存在数据依赖性,处理器能够改变语句对应机器指令的执行顺序。
内存系统的重排序。因为处理器使用缓存和读/写缓冲区,这使得加载和存储操做看上去多是在乱序执行。
从java源代码到最终实际执行的指令序列,会分别经历下面三种重排序:
源代码 -- 编译器优化重排序 -- 指令级并行重排序 -- 内存系统重排序 -- 最终执行
3.数据竞争
在业务程序执行时,当存在数据共享时,在代码实际执行之时,咱们并不能保证彻底按照咱们所写的代码语句顺序的执行(没有lock的状况下)。大多数多线程出现问题的地方在于,咱们并不知道咱们读取到的是否是咱们原先但愿处理的数据,有可能咱们但愿发生的修改尚未发生,或者咱们但愿读取到的数据已经被别的线程修改过了。
4.总线事务
在计算机中,数据经过总线在处理器和内存之间传递。每次处理器和内存之间的数据传递都是经过一系列步骤来完成的,这一系列步骤称之为总线事务。总线事务包括读事务和写事务。读事务从内存传送数据处处理器,写事务从处理器传送数据到内存,每一个事务会读/写内存中一个或多个物理上连续的字。这里的关键是,总线会同步试图并发使用总线的事务。在一个处理器执行总线事务期间,总线会禁止其它全部的处理器和I/O设备执行内存的读/写。在任意时间点,最多只能有一个处理器能访问内存。这个特性确保了单个总线事务之中的内存读/写操做具备原子性。
java语言规范鼓励但不强求JVM对64位的long型变量和double型变量的读/写具备原子性。当JVM在这种处理器上运行时,会把一个64位long/ double型变量的读/写操做拆分为两个32位的读/写操做来执行。这两个32位的读/写操做可能会被分配到不一样的总线事务中执行,此时对这个64位变量的读/写将不具备原子性。假设处理器A写一个long型变量,同时处理器B要读这个long型变量。处理器A中64位的写操做被拆分为两个32位的写操做,且这两个32位的写操做被分配到不一样的写事务中执行。同时处理器B中64位的读操做被拆分为两个32位的读操做,且这两个32位的读操做被分配到同一个的读事务中执行。当处理器A和B按上图的时序来执行时,处理器B将看到仅仅被处理器A“写了一半“的无效值。