第1章 并发编程的挑战
-
并发编程中,需考虑的3大问题:
-
上下文切换开销。
处理器的每核都使用时间片分配算法给每线程分别分配其可允许执行的时长,使每线程都能执行一小段,实现循环往复地切换线程,执行多任务。
线程从开始执行态到暂存态,再到恢复执行态的这种状态变换,被称为上下文切换。 -
线程死锁。
若干线程互相持有对方的锁,均无法释放导致线程死锁。
-
软硬件资源限制。
多线程的并发程度,受限于软硬件资源。
-
-
关于上下文切换开销
- 开销及影响:
-
线程数,影响上下文切换频率。
若在软硬件资源匮乏的环境中运行大量线程,不但不会更快,而且还会因存在大量 WAITTING 态的线程,以及其会往复在 WAITTING 态与 RUNNABLE 态做上下文切换的操作,严重降低性能。
-
上下文切换频率,影响程序的执行性能。
-
- 减少开销的方法:
-
无锁并发编程。
线程间无锁执行,就不会有大量线程在阻塞等待,也就不会有频繁的上下文切换,于是降低上下文切换开销。
-
CAS(Compare And Swap,即比较并交换)算法。
使用无需加锁的 java.util.concurrent.atomic 包内的原子类来操作数据。
-
使用最少线程。
避免不合理地创建线程。
线程过多,易造成大部分线程均处于 WAITTING 态,导致频繁的上下文切换。 -
使用协程。
即:在单线程中维持多任务的切换和调度。
-
- 开销及影响:
第2章 并发机制的底层实现原理
-
Java中的并发机制,依赖于 JVM 实现和 CPU 指令。
-
关于 volatile 关键字
-
作用:
保证在多核处理器下共享变量的可见性。可见性:当某线程修改某共享变量后,另一线程能读到修改后的值。
-
特点:
- 轻量级的 synchronized。
- 比锁更方便。
-
保证可见性的方法:
-
JVM在汇编代码中,插入 lock 指令。
JVM 为将声明为 volatile 型变量的汇编代码增加了一行以 lock 为前缀的指令,以告诉CPU需将其高速缓存内的值写回内存。
-
CPU架构的特性:缓存一致性协议和嗅探技术。
- 为确保在多核 CPU 上各核的缓存一致性,各核会实现缓存一致性协议。
同时,CPU 会持续嗅探在总线上传播的数据,以判断自己高速缓存中缓存的数据的有效性。 - 因各 CPU 核实现了嗅探技术,若某一CPU核将数据从高速缓存写回了内存,则该数据在其余核的缓存都将失效。
- 为确保在多核 CPU 上各核的缓存一致性,各核会实现缓存一致性协议。
-
-
-
关于 lock 指令
-
概述:
可使 CPU 锁定特定内存地址的 CPU 指令。 -
特点:
lock 信号声明期间,CPU 可独占任何共享的内存。 -
处理器能确保基本内存操作的原子性的技术手段:
作用 锁定目标 优点 缺点 适用场景 总线锁 在 硬件层面
确保基本内存操作的原子性CPU总线
即:锁整个CPU或某些CPU核实现简单 开销大,性能低 1. 数据无法缓存在CPU高速缓存中。
2. 仅支持总线锁的老处理器。缓存锁 已在 CPU 高速缓存中缓存了的内存区域
即:锁缓存开销小,性能高 实现复杂
需各 CPU 核实现缓存一致性协议1. 数据可缓存在CPU高速缓存中。
2. 支持缓存锁的新处理器。- 若缓存锁无法使用,则降级使用总线锁。
- 锁定了 CPU 总线,就意味着锁定期间其无法再经总线访问内存了。
-
-
小总结:关于 CPU 的总线锁、缓存锁和缓存一致性协议
前2者确保原子性;后者确保可见性。 -
关于 synchronized 关键字
-
关于 Java 对象头
- 概述:
存储Java对象的基本信息,是对象的身份证。 - 组成:
- 概述:
-
关于 Java 锁
- 锁状态及等级分类:
- 出现锁等级的原因:
- 减少加解锁带来的性能消耗。
- 适应不同需锁的环境。
- 对比:
锁类型 原理 优点 缺点 适用场景 备注 备注 偏向锁 加锁:
在对象头和栈帧中记录锁偏向的线程ID,并在以后均以此来判断某线程是否有锁,而不必真正去加锁。
若未检测到锁偏向的线程ID,则使用CAS锁(即比较并交换,是非阻塞式的乐观锁,是竞争锁)。
解锁:
有其他线程竞争偏向锁时,解偏向锁。加解锁无性能消耗 竞争才能解锁,有性能消耗 对象间极少或几乎无竞争的零并发度场景
如单线程访问同步代码块。1. 是 JDK 默认启用
的锁。
2. 使用 JVM 参数 -XX:-UseBiasedLocking-false 可关闭偏向锁并切换到轻量级锁。锁自旋、CAS 锁,均会使等待加锁的 - 锁状态及等级分类: