4.1 CPU物理缓存结构
L1缓存离 CPU 最近也最快,一般就 32k/64k水平;L2 缓存速度次之,容量一般比L1大;L3级缓存最大,比前面二者都大,速度也最慢,大小可能 12M 的水平。
4.2 并发编程的三大问题
三大问题分别是: 原子性、可见性、有序性 问题。必须要保证这 3 个,只要有一个没保证,在多线程情况下就可能不正确。
后续的略。
4.3 硬件层的MESI协议原理
为了缓解内存速度和CPU速度差问题,现在计算机都会为CPU添加高速缓存,每个CPU内核都有自己的一级、二级高速缓存,同一个CPU多个内核之间共享一个三级高速缓存。
4.3.1 总线锁和缓存锁
CPU的处理流程为:现将计算需要用到的数据缓存到CPU的高速缓存中,CPU计算时,直接从高速缓存获取数据并在计算完成后写会高速缓存,整个运算完成后再把高速缓存的数据同步回主存。由于每个线程可能运行在不同的CPU内核中,因此同一份数据可能被缓存到多个CPU内核中,就会发生内存可见性问题。
后续的略。
4.4.1 重排序
编译器为什么要重排序?其目的为: 与其等待阻塞指令(如等待缓存刷入),不如先去执行其他指令。另外,CPU层面也有重排序。
4.5 JMM 详解
JMM (Java Memory Model ,Java 内存模型) 并不像JVM 内存结构一样是真实存在的运行实体,更多体现为一种规范和规则。
JMM 定义了一组规则或规范,该规范定义了一个线程对共享变量写入时,如何确保对另一个线程是可见的,实际上 JMM 提供了合理的禁用缓存以及禁止重排序的方法,所以其核心价值在于解决可见性和有序性。它的另一大价值在于:屏蔽各大硬件和操作系统差异,保证 Java 程序在各大平台堆内存访问是一致的。
JMM 规定所有的变量都存储在主存(类似于物理内存,但是有区别)中,每个 Java 线程都有自己的工作内存(类似于CPU高速缓存,但有区别)。
JMM 提供了一套自己的方案解决可见性和有序性问题,包括 volatile、synchronized、final 等。
4.7 volatile 不具备原子性
对于关键字 volatile 修饰的内存可见变量而言,具有2个重要的语义:
使用 volatile 修饰的变量在变量的值发生改变时,会立刻同步到主存,并使其他线程的变量副本失效
禁止指令重排序
使用++操作说明volatile不具备重排序功能:
A、B线程分别运行在Core1 和 Core2 核上,假设此时共享value 的值为 0,现在线程 A、B 都读取value值到自己的工作内存上
线程 A 将 value 的值变为 1,完成了 assign、store 操作,假设在执行 write 指令前 A 的时间片用完,线程 A 被空闲但是 write 操作还没达到主存,但是呢, store 操作触发了写的信号,导致了 B 缓存过期
B重新从主存读到 value,可想而知这时候还是 0
线程 B 执行完所有操作,将 value 值变成 1 写入主存
线程 A 重新拿到时间片,将过期了的 1 写入主存
所以,对于复合操作,volatile无法保障原子性,如果要保证复合操作的原子性,就需要用到锁。