1. 基础术语
前言:
Java代码被编译器编译为Java字节码代码,字节码被类加载器加载大JVM里,JVM执行字节码,最终转换为汇编指令被CPU执行。Java的并发机制依赖于JVM和CPU指令。
CPU常用术语
| 术语 | 英文名称 | 描述 |
|---|---|---|
| 内存屏障 | memory barries | 是一组处理器指令,用户实现对内存中操作的顺序限制 |
| 缓冲行 | cache line | 缓存中可以分配的最小存储单位,处理器填写缓存行时会加载整个缓存 行,需要使用多个主内存读取周期 |
| 原子操作 | atomic operations | 不可操作的一个或者一系列操作 |
| 缓存行填充 | cache line fill | 当处理器识别到从内存中读取操作数是可缓存的,处理器读取整个缓存 行到适当的缓存 |
| 缓存命中 | cache hit | 如果进行高速缓存行填充的位置仍然是下次处理器访问的位置时,处理 器从缓存中读取操作数,而不是从内存中读取操作数 |
| 写命中 | write hit | 当处理器将操作数写回到一个内存缓存区域的时候,它首先会检查这个内存 缓存地址是否存在缓存行中,如果存在一个有效的缓存行,则处理器 将这个数据写会缓存,而不是写会内存。这个操作叫做写命中 |
| 写缺失 | write miss the cache | 一个有效的缓存行被写入到一个不存在的内存区域 |
2. validate如何保证内存可见性
validate是轻量级的synchronize,它比synchronize的使用和执行成本更低,因为他不会引起上下文的切换和调度。
Lock前缀指令会引起处理器缓存回写到内存
一个处理器的缓存回写到内存会导致其他处理器的缓存失效
被validate修饰的变量,转换为汇编后会生成2条Lock前缀的指令。
为了提高处理速度,处理器不直接和内存进行通信,先将系统内存中的数据读取到内部缓存再进行操作。如果对声明了validate前缀的变量进行操作,jvm会向处理器发送一条lock前缀指令,将这个变量所在缓存行的数据写回系统内存。多处理器环境下,为了保证其他处理器读取到的缓存是正确的,各个处理器都要实现缓存一致协议。
每个处理器会通过不断的嗅探总线上的数据来判断自己缓存中的值是否有效。当嗅探到自己缓存中的地址失效时,会将缓存中的值设为不可用。
当处理器需要再次对缓存值进行处理时,直接从系统内存中读取,再把操作后的值写会缓存。
3. synchronized同步原理
java中每个对象都可以作为锁
- 普通同步方法,锁的是当前的实例对象
- 同步代码块,锁的是synchronize里配置的对象
- 静态方法,锁的是当前类的class对象
当一个线程试图访问同步代码时,必须先获取到锁,退出或异常时必须释放锁。
jvm基于进入和退出MOnitor对象实现对方法和代码块的同步。代码编译后在同步方法开始的地方会有一个monitorenter指令,方法结束和异常处会有一个monitorexist指令。jvm保证每个monitorenter有个monitorexist与之配对。
任何一个对象都有一个monitorenter与之对应,当一个monitor被持有后,对象处于锁定状态。
代码执行到monitorenter指令后,会尝试获取对象对应的monitor的持有权,即尝试获取对象的锁。
4. 原子操作实现原理
4.1 常用术语
| 名字 | 英文名 | 含义 |
|---|---|---|
| 缓存行 | Cache Line | 缓存的最小操作单位 |
| 比较并交换 | Compare and Swap | CAS需要传入两个值(旧值,新值)。在操作期间先比较旧值有没有发生变化,如果旧值发生了变化停止交换操作。如果旧值没有发生变化,进行交换操作 |
| CPU流水线 | CPU pipline | 在CPU中5~6个不同的单元组成一条指令处理流水线,提高CPU效率 |
| 内存顺序冲突 | Memory order violation | 内存顺序冲突一般是由假共享引起的。不同的CPU同事处理一个缓存行的不同部分,导致其中一个CPU的编辑失效。当出现这种情况时,必须情况CPU流水线 |
4.2 使用总线锁保持原子性
总线锁使用处理器提供的一个LOCK#信号,当一个处理器在总线上输出该信号时,其他处理器的请求将会被阻断,该处理器独享共享内存
5. 总结
- validate内存可见性原理:被validate修饰的变量每次变更后,都会将变更同步到系统内存。被validate修饰的变量每次使用前从系统内存进行读取
- synchronized同步原理:基于锁的获取和