【并发编程】并发编程的基础

目录

硬件模型

CPU多级缓存

MESI -CPU缓存一致性协议

JAVA内存模型

8种操作

但是对于volatile修饰的变量有一些特殊规则


 

硬件模型

        学习并发之前,我们要先简单了解一下计算的硬件模型。

【并发编程】并发编程的基础_第1张图片

程序加载到主存中之后,先加载到CPU高速缓存中待命,甚至会加载到CPU寄存器当中,让CPU进行处理。主存的处理速度是远远不如CPU的,所以中间加了一个CPU高速缓存,配合CPU的速度,定时和主存同步数据(因为主存跟不上CPU的频率,经常等待主存),另外为了让处理器内部运算单元尽可能的充分利用,处理器还会对输入的代码进行乱序执行,但是保证结果是一致的。而寄存器是CPU内存的基础,每个CPU都会包含一系列的寄存器。CPU在寄存器中的处理速度要比在主存中效率高。

 

CPU多级缓存

推荐文章

我们把CPU比喻成一个大型加工总部,内存为部件存储大仓库,而缓存就是总部与大仓库之间的小仓库,离CPU较近的小仓库是一级缓存,其次依次为二级缓存和三级缓存,当加工总部需要加工某个成品时候需要很多部件,这个时候缓存就是把所需要的部件提前从内存调出,存储在小仓库内,当总部加工需要某个部件时候就可以直接从最近的小仓库提取,就不必大费周章去内存大仓库调取。

 

既然存在多个缓存区域,那么怎么保证缓存数据的一致性呢?

MESI -CPU缓存一致性协议

MESI(modified Exclusive shared or invalid)是一种广泛使用的支持回写策略的缓存一致性协议。推荐文章

 CPU缓存行使用4种状态进行标记,进而将数据进行同步或失效来保持多级缓存的一致。

状态转化图

【并发编程】并发编程的基础_第2张图片

        

M:被修改(modified) 此数据被修改,但未同步到主存中 被写回到主存中,状态变成独享(E)
E:独享(Exclusive) 未被修改过,与主存一致的数据 被读取时,变成共享(S)
S:共享(shared) 被多个CPU缓存过,与主存一致 被修改之后,其他CPU中的此缓存失效(I)
I:失效(Invalid) 缓存无效  

 

JAVA内存模型

Java Memory Model,JMM是JVM规范定义的一个抽象的概念。他屏蔽了Java程序在不同硬件和OS对内存访问的差异,实现了在不同平台上都能达到内存访问的一致性。主要是围绕如何处理原子性、可见性、顺序性这三个特征来设计的。

 

JVM的内存模型不同于JMM内存模型。JMM主要是为了定义程序中的共享变量的访问规则,即在虚拟机将变量存储(或取出)到主内存的底层细节。注意,像局部变量和方法参数这种线程私有的变量不包含在内。(可以把JMM的范围比作JVM中的堆存放的数据规则)。参考文章

 

那Java内存模型到底是什么呢?

                                           【并发编程】并发编程的基础_第3张图片

说明:JVM规定共享变量都在主内存中产生,而JVM的每个线程都有自己的工作内存,是私有的,每个线程对共享变量修改的时候,都是在工作内存中对副本进行修改,然后经过JMM控制命令进而同步到主内存中来保持数据的一致性,而多个线程操作的这些同步变量都是经过主内存来进行传递同步的,有8种操作命令来完成。每种操作必须是原子性的

 

8种操作

lock(锁定) 锁定主内存中的变量,只能供一个线程用
unlock(解锁) 释放主内存中的变量,其他线程才可以上锁
read(读取) 把主内存的变量传输到线程的工作空间
load(载入) 把read到工作空间的值,放到变量副本中
use(使用) 把工作内存中的变量传递给执行引擎
assign(赋值) 把执行引擎执行的结果赋值给变量副本
store(存储) 把工作内存中的变量传递给主内存
write(写入) 吧store到主内存中的变量值放到变量中

图例表示

                  【并发编程】并发编程的基础_第4张图片

 

这几种操作之间也有一定的同步规则(执行顺序)

  1. 如果要把一个变量从主内存传输到工作内存,那就要顺序的执行read和load操作,如果要把一个变量从工作内存回写到主内存,就要顺序的执行store和write操作。对于普通变量,虚拟机只是要求顺序的执行,并没有要求连续的执行
  2. 不允许read和load、store和write操作之一单独出现
  3. 不允许一个线程丢弃最近的assign操作,也就是不允许线程在自己的工作线程中修改了变量的值却不同步/回写到主内存
  4. 不允许一个线程回写没有修改的变量到主内存
  5. 变量只能在主内存中产生,不允许在工作内存中直接使用一个未被初始化的变量,也就是没有执行load或者assign操作。也就是说在执行use、store之前必须对相同的变量执行了load、assign操作
  6. 一个变量在同一时刻只能被一个线程对其进行lock操作
  7. 对变量执行lock操作,就会清空工作空间该变量的值
  8. 不允许对没有lock的变量执行unlock操作
  9. 对一个变量执行unlock之前,必须先把变量同步回主内存中

但是对于volatile修饰的变量有一些特殊规则

线程的安全性

               首先,多线程的意义是什么呢?目的在于最大限度的利用CPU资源。因为CPU速度太快了,但是我们IO速度,网络速度,数据库连接等跟CPU的速度相比较来说差太远,于是用多线程来减少CPU的空闲时间。但是在某个时刻内,CPU实际上只能执行一个线程,外部看起来是用了多线程,其实是操作系统对进程线程进行了管理,分配每个进程的时间,而每个进程内,程序自己处理线程的时间分配。就这样,多个线程来回进行切换调度。

               凡事有利有弊,那么多线程自然也带来了一些缺憾需要弥补,那就是线程的安全性。经常谈的是原子性,可见性和有序性。

【并发编程】并发编程的基础_第5张图片

      那么如何保证线程的安全性呢?

    原子性

    【并发编程】并发编程的基础_第6张图片

    涉及问题

   1、CAS(compare and swap) 的ABA问题

         CAS算法:

         ABA问题:如果线程A对变量a初次读取的时候是1,并且在准备赋值的时候检查到它仍然是1,那这中间很可能出现一种情况是线程B刚好把变量a从1设置成2,又再次设置成1,所以线程A看到的初始值1很可能已经被修改过,会有问题。

       如何解决?可以加版本号,推荐文章

 

   2、synchronize

         是通过底层系统指令来实现的,JDK1.6对其进行了优化,在其之前synchronize的性能很差,是重量级的锁,不如ReentranLock。但是1.6之后,引入了偏向锁等,提高了性能,在一般情况下还是建议使用synchronize。如果需要利用到Lock特性的时候,再使用Lock。

         底层主要是通过       

 

 

你可能感兴趣的:(【并发编程】并发编程的基础)