(二)Java多线程 —— 并发编程的基础

并发编程的基础

一、 CPU多级缓存

(二)Java多线程 —— 并发编程的基础

1)为什么需要CPU cache:

CPU的频率太快了,主存跟不上,这样在处理器时钟周期内,CPU常常需要等待主存,浪费资源。
所以cache的出现,是为了缓解CPU和内存之间速度的不匹配问题(结构:cpu -> cache -> memory).

2)CPU cache有什么意义:

  1. 时间局部性:
    如果某个数据被访问,那么在不久的将来它可能被再次访问。
  2. 空间局部性:
    如果某个数据被访问,那么与它相邻的数据很快也可能被访问。

3)CPU 多级缓存 - 缓存一致性(MESI):

(二)Java多线程 —— 并发编程的基础
用于保证多个CPU cache之间缓存共享数据的一致。

  1. 四种状态:
  • Modified(被修改):该缓存行只被缓存在该CPU的缓存中,并且是被修改过的,因此与主存中的数据是不一致的。该缓存行中的内存需要在未来的某个时间点写回主存,
    在这个时间点是允许其他CPU读取主存中相应的内存之前,当这里面的值写回主存之后,该缓存中的状态将会变成“独享”状态.

  • Exclusive(独享):该缓存行只被缓存在该CPU的缓存中,是未被修改过的,与主存中的数据是一致的。当有其他CPU读取该内存时,会变成“共享”状态。
    同样的,当其他CPU修改该内存的值,则会变成“被修改”状态。

  • share(共享):意味着该缓存行可能被多个CPU进行缓存,并且各个缓存的数据与主存中数据是一致的。当有一个CPU修改该缓存行的时候,其他CPU中该缓存行是可以变成“无效”状态。

  • invalid(无效的):该缓存是无效的,可能是有其他CPU修改了该缓存行。

  1. 四种操作:
  • local read:读本地缓存中的数据。
  • local write:将数据写到本地缓存。
  • remote read:将内存中的数据读取过来。
  • remote write:将数据写回到主存中去。

4)CPU 多级缓存 - 乱序执行优化:

处理器为提高运算速度二做出违背代码原有顺序的优化。
(二)Java多线程 —— 并发编程的基础

二、Java内存模型(Java Memory Model,JMM):

(二)Java多线程 —— 并发编程的基础

1)Thread Stack(线程栈):

主要存放基本类型的变量int short double 对象句柄,本地变量等等。
内存大小生存期是确定的,速度快,仅次于计算机的寄存器

2)Head(堆):

主要存储运行的数据对象,由垃圾回收管理
内存大小动态分配,速度会慢一点

如果两个线程调用同一个对象
都会拥有这个对象的私有拷贝。

3)硬件架构:

(二)Java多线程 —— 并发编程的基础
(二)Java多线程 —— 并发编程的基础
每个CPU都包含一系列的寄存器(CPU Regusters)速度远大于在主存上的运行速度。

  • 运行原理:
    CPU需要读取主存时,会将数据加载到CPU Cache Memory高速缓存中,然后再读取到寄存器里面,在寄存器里面执行操作,将执行的结果刷新到高速缓存中,最后在某个时间点刷新回主存。

  • Java线程之间通信:
    线程A 与 线程B之间要进行数据通信时,需要把本地内存A中更新过的共享变量,刷新到主内存中,线程B中去主内存中读取,才能进行实现同步共享。

  • Java内存模型抽象结构图:
    (二)Java多线程 —— 并发编程的基础

4)Java内存模型 - 同步八种操作:

lock(锁定) -》unlock(解锁)-》read(读取)-》load(载入)-》use(使用)-》assign(赋值)-》store(存储)->write(写入)

5)Java内存模型 - 同步操作与规则:

(二)Java多线程 —— 并发编程的基础

操作:

主内存变量【lock】 ===>【read】Save/Load =>【load】工作内存=>【use】Java线程

【unlock】 主内存变量【write】 <===Save/Load【store】 <===工作内存【Assign】<===Java线程

规则:

  1. 不允许read/load 或 write/store 单一操作,他们一个组合,而且是按顺序执行的,允许不连续执行,可插入其他指令。
  2. 不允许线程丢弃调assign操作,必须把变化同步给主内存。
  3. 不允许线程无发生assign操作,去同步给主内存。
  4. 一个新的变量必须从主内存中诞生,不允许在工作内存使用一个未初始化的变量。
  5. 一个变量只允许一条线程对其进行lock操作。但是可以被lock的这条线程执行多次,执行多次lock就要执行多少次unlock才会解锁。
  6. 一个变量执行lock操作,它将会清空工作内存中此变量的值,重新执行load或assign来获得变量的值。
  7. 没有lock的变量不允许执行unlock操作,也不能unlock被其他线程的lock的变量。
  8. 执行unlock之前必须把变量值同步回主内存中。

三、并发的优势与风险

(二)Java多线程 —— 并发编程的基础

优势:

  1. 速度:同时处理多个请求,响应更快,复杂的操作可以分成多个进程同时进行
  2. 设计:程序设计在某些情况下更简单,也可以有更多的选择。
  3. 资源利用:CPU能够在等待IO的时候做一些其他的事情。

风险:

  1. 安全性:多个线程共享数据时可能会产生与期望不相符的结果。
  2. 活跃性:某个操作无法继续进行下去时,就会发生活跃性问题,比如死锁、饥饿等问题。
  3. 性能:线程过多时会使得:CPU频繁切换,调度时间增多,同步机制;消耗过多内存。

总结

  • CPU多级缓存:缓存一致性、乱序执行优化
  • Java内存模型:JMM规定、抽象结构、同步八种操作及规则
  • Java并发的优势与风险