一、概念
| 知识点 | 核心内容 | 关键点/易混淆点 |
|---|---|---|
| 并发编程基础 | 并发编程入门知识,包括线程安全、原子操作等 | 线程安全与原子操作的区别与联系 |
| 原子操作 | 通过CPU支持的CAS指令实现原子性 | CAS指令与synchronized关键字的性能比较 |
| CAS操作 | 比较并交换(Compare and Swap) | ABA问题及其解决方案(版本戳) |
| 线程封闭 | 将对象封装到线程内部,实现线程安全 | 局部变量与全局变量的线程安全性 |
| 无状态类 | 没有成员变量的类,一定是线程安全的 | 传入对象作为参数时的线程安全性问题 |
| 不可变类 | 使用final关键字修饰成员变量,使其不可变 | final关键字修饰对象时,对象内部数据仍可变 |
| 死锁 | 多个进程因竞争资源而造成阻塞现象 | 死锁产生的三个必要条件:互斥、请求和保持、不剥夺和环路等待 |
| 活锁 | 线程不断尝试获取锁,但始终无法成功,导致无限循环 | 活锁与死锁的区别:活锁线程处于可运行状态,死锁线程处于阻塞状态 |
| 线程饥饿 | 线程因优先级低而始终无法获取CPU执行时间 | 线程饥饿与死锁、活锁的区别 |
| LongAdder | 通过写热点分散的方式提升原子操作的性能 | LongAdder与AtomicInteger的性能比较及适用场景 |
二、实现原理
原子操作
- 原子操作: 原子操作是指不可分割的操作,即操作在执行过程中不会被其他操作打
- 举例: 类似于我们平时说的“事物”,事物具有四大特性,即ACID特性,其中的A就是Atomic,表示原子性,即事务中的操作要么全部完成,要么全部不完成,不会停留在中间某个状态。
2)CAS实现原子操作 16:03
- 原子操作在事务中的体现 16:13
-
- 原子性定义: 原子性指的是一个事务中的操作要么全部完成,要么全部不完成,是一个不可分割的整体。
- 事务中的原子性: 在事务中,从begin开始到commit结束,中间的所有操作在外部看来要么全部执行完成,要么全部未执行(若rollback)。
- 并发中的原子性 17:07
-
- 并发原子性: 在并发编程中,一个线程的操作对于其他线程来说,要么全部可见,要么全部不可见,具有原子性。
- 例子: 线程A和线程B各自执行一系列操作,对于线程B来说,线程A的操作要么全部执行完,要么全部没执行,反之亦然。
- 使用锁实现原子性 17:53
-
- 锁机制: 使用锁(如synchronized关键字)可以确保一段代码在执行时,其他线程无法进入,从而实现原子性。
- 实现方式: 将需要原子执行的代码块用锁关键字包围,确保这段代码要么全部执行完,要么不被执行。
- 锁机制的不足 18:27
-
- 阻塞问题: 锁机制会导致未获得锁的线程阻塞,等待锁的释放,可能影响高优先级线程的执行。
- 锁不释放问题: 如果获得锁的线程在执行过程中出现问题,可能导致锁一直不释放,造成其他线程无限等待。
- 死锁问题: 锁机制可能导致死锁,即两个或多个线程相互等待对方释放锁。
- 性能问题: 锁机制是一种比较粗糙、力度较大的操作,对于简单的操作(如累加)来说,加锁的开销可能远大于操作本身的开销。
- 引入Atomic系列: 为了解决简单操作加锁过于笨重的问题,Java提供了Atomic系列的原子操作类,这些类通过CAS(Compare-And-Swap)机制实现原子操作,无需加锁,性能更高。
3)Jdk中相关原子操作类的使用 20:54
- 原子操作类的实现机制 21:27
-
- 实现机制: Java中的原子操作类是通过利用当前处理器普遍支持的CAS(Compare-And-Swap)指令来实现的。例如,Intel的汇编指令cmpxchg。
- CAS指令: CAS操作包含三个运算符:一个内存地址V,一个期望的值A和一个新值B。当内存地址V上的值等于期望的值A时,将该地址上的值更新为新值B,否则不进行任何操作。
- CAS指令与原子操作 22:01
-
- CAS指令: CAS指令,如Intel的cmpxchg,是一种CPU指令,用于实现无锁并发控制。不同厂家的具体实现算法可能不同,但原理基本一致。
- 原子操作: 通过CAS指令,可以保证比较和交换这两个操作是原子的,即在CPU指令级别上不可分割,从而避免了多线程环境下的竞态条件。
- 应用实例: 在秒杀库存扣减等场景中,使用CAS指令可以保证库存扣减操作的原子性,避免库存超卖等问题。
- CAS操作过程 25:18
-
- 操作过程: CAS操作包含三个关键参数:内存地址V、期望值A和新值B。当地址V上的值等于期望值A时,将地址V上的值更新为新值B;否则,不进行任何操作,并返回原值。
- 循环CAS: 由于CAS操作可能不成功(例如,当地址V上的值不等于期望值A时),因此在实际应用中,通常会使用循环CAS,即在一个循环中不断尝试CAS操作,直到成功为止。
- Java中的实现: Java中的Atomic系列原子操作类就是利用了循环CAS来实现无锁并发控制的。
- 举例: 例如,对变量count进行累加操作,可以将count的内存地址设置为V,原值设置为A,新值设置为B(A+1)。然后,通过CAS指令进行比较和交换,实现count的原子累加。如果CAS操作失败,则继续循环尝试,直到成功为止。
4)CAS指令与原子操作 27:00
- Atomic系列原子操作类 27:13
- CAS操作过程与运算符 28:34
- 循环CAS与原子变量操作 31:02
- 多线程下的CAS操作示例 31:50
- CAS操作性能优势 38:00
- CAS原子性保证 39:06
-
- 原子性保证: CAS操作是由操作系统或CPU专门提供的指令,在硬件级别保证了原子性。
- 原因: 这是由CPU处理器专门提供的支持CAS操作的指令所保证的。
- Atomic系列类的方法
-
- 方法示例:
-
-
- getAndSet(int newValue): 原子地设置为给定值,并返回旧值。
- compareAndSet(int expect, int update): 如果当前值等于期望值,则原子地设置为新值,并返回成功或失败。
- getAndIncrement(): 原子地递增当前值,并返回旧值。
-
-
- 底层实现: 这些方法最终都会调用Unsafe类中的compareAndSwap相关方法,这些方法是native的,基于操作系统或CPU指令实现。
5)线程安全性详解 39:42
- synchronized关键字的局限性
-
- 阻塞锁机制: synchronized关键字是基于阻塞的锁机制,一个线程拥有锁时,其他线程需等待。
- 问题与局限:
-
-
- 高优先级线程被阻塞时无法优先执行。
- 线程可能长时间不释放锁,导致其他线程长时间等待。
- 可能出现死锁等复杂情况。
- 锁机制粒度大,对于简单需求如计数器过于笨重。
-
- Atomic系列原子操作类
- CAS操作的问题
-
- ABA问题: CAS操作可能引入ABA问题,即值从A变到B又变回A,但CAS操作无法感知中间的变化。
- 其他潜在问题: 虽未详细展开,但应留意CAS操作可能带来的其他潜在问题。
CAS与Atomic原子操作解析
- 原子操作的概念 40:04
-
- 原子操作: 原子操作是不可被中断的一个或一系列操作。
- CAS实现原子操作的三大问题
-
- ABA问题
-
-
- ABA问题定义: 在执行CAS操作前,变量的值由A变为B,又变回A,导致CAS操作误以为变量值未变而成功执行。
- ABA问题示例: 类似于你倒了一杯水,回来时水还在,但中间可能被人喝过,你又拿起来喝,这就是ABA问题。
- 解决方法: 引入版本号或类似机制,确保在变量值变化时能够检测到。
-
-
- 循环时间长开销大
-
-
- 问题描述: CAS操作需要不断循环比较并尝试更新,如果变量值长时间不变或竞争激烈,会导致循环次数增加,开销增大。
- 解决方法: 优化算法,减少不必要的循环;或采用其他并发控制机制。
-
-
- 只能保证一个共享变量的原子操作
-
-
- 问题描述: CAS机制通常只能保证对一个共享变量的原子操作,对于多个共享变量的原子性操作需要额外的机制来保证。
- 解决方法: 采用更复杂的并发控制机制,如锁、事务内存等。
-
- 原子操作类的使用
-
- AtomicInteger: 用于原子性地更新整数值。
- AtomicIntegerArray: 用于原子性地更新整数数组中的元素。
- AtomicReference: 用于原子性地更新引用类型变量。
- AtomicStampedReference: 带有版本号的原子引用,可以解决ABA问题。
- AtomicMarkableReference: 带有标记位的原子引用,也可以用于解决某些并发问题。
- AtomicUpdateField类: 如AtomicIntegerFieldUpdater等,用于原子性地更新对象的某个字段。
- 总结: 这些原子操作类提供了高效的线程安全操作,避免了传统锁机制带来的性能开销和复杂性。
活锁与线程饥饿
- 版本戳与ABA问题 42:33
-
- 版本戳的定义与作用
-
-
- 版本戳定义: 版本戳是一个用于标记数据版本或状态的标识符,通过在数据值后附加一个额外的标志或变量(如数字)来实现。
- 作用: 版本戳用于解决并发编程中的ABA问题,即当某个值在比较和交换(CAS)操作期间被其他线程修改并又改回原值,但实际上已不是期望的原始状态。
- 示例说明: 初始值为a,存放时加上版本戳变为a1。若线程将其改为c后再改回a,此时版本戳变为a3。CAS操作时,期望值为a1,但实际为a3,因此识别出状态变化。
-
-
- ABA问题及其影响
-
-
- ABA问题定义: 在CAS操作中,一个值A被线程修改成B后又改回A,但由于版本戳的变化,CAS操作能识别出这种变化,从而避免错误地认为值没有变化。
- 影响分析:
-
-
-
-
- 大多数情况下无影响: 对于简单的计数操作(如count++),中间值的变化(如从0变为100再变回1)通常不关心,只关注最终结果。
- 特定场景有影响: 在需要精确控制状态变化的场景中,ABA问题可能导致错误判断,因此需要版本戳等机制来解决。
-
-
-
- CAS解决ABA问题的价值
-
-
- 价值体现: CAS操作结合版本戳,能有效解决ABA问题,确保在并发环境下数据的一致性和正确性。
- 架构领域参考: 这种解决ABA问题的方法在架构设计中具有广泛的参考价值,特别是在需要高效并发控制的场景中。
-
-
- 注意事项
-
-
- 了解ABA问题: 虽然大多数情况下ABA问题对业务操作无影响,但开发者应了解并认识这一问题。
- 适用场景判断: 在设计并发控制逻辑时,需根据具体场景判断是否需要考虑ABA问题及其解决方案。
-
- CAS解决ABA问题 44:48
-
- ABA问题概述
- ABA问题的解决方法
- CAS操作的其他问题
- 并发环境下的特别注意
-
-
- 特别注意ABA问题: 在并发环境下,由于多个线程可能同时操作同一个变量,因此需要特别注意ABA问题,确保数据的正确性和一致性。
- 其他并发问题: 除了ABA问题外,还需要关注死锁、活锁、饥饿等其他并发问题,以确保系统的稳定性和性能。
-
- 循环时间长与开销大 45:31
-
- CAS操作与CPU消耗
-
-
- CAS操作原理: CAS(Compare-And-Swap)是一种用于实现多线程同步的原子操作。它通过比较内存地址上的值与期望值,若相等则更新为新值,否则不做任何操作。
- CPU消耗问题: 当大量线程竞争同一个资源时,CAS操作可能会频繁失败,导致线程不断重试(自旋),从而大量消耗CPU资源。
- 与synchronized关键字对比: 在高度竞争的情况下,synchronized关键字的性能可能优于CAS。因为synchronized会使未获得锁的线程进入阻塞状态,不会消耗CPU资源;而CAS则会使线程空转,消耗大量CPU时间。
-
-
- CAS与锁升级
-
-
- 锁升级过程: synchronized关键字底层存在锁升级的过程,从偏向锁到轻量级锁,再到重量级锁。
- 适用场景: 偏向锁和轻量级锁适用于竞争不激烈的情况;而重量级锁则在竞争激烈时使用。CAS操作在竞争激烈时,由于自旋消耗CPU,性能可能不如synchronized。
-
-
- ABA问题
-
-
- ABA问题定义: 当一个值从A变为B再变回A时,CAS操作无法检测到这种变化,但实际上值已经发生了变化。
- 解决思路: 使用版本号(如AtomicStampedReference类),在变量前追加版本号,每次变量更新时版本号加1,从而解决ABA问题。
-
-
- 只能保证一个共享变量的原子操作
-
-
- 限制: CAS操作只能保证一个共享变量的原子性。当需要操作多个共享变量时,CAS无法保证整体操作的原子性。
- 解决方案:
-
-
-
-
- 将多个变量合并成一个对象,然后对该对象进行CAS操作。
- 使用JDK提供的AtomicReference类,将多个变量放在一个对象中进行CAS操作。
-
-
-
- LongAdder类
-
-
- 引入背景: 在高并发情况下,AtomicInteger等原子操作类由于所有操作集中在某个变量上,可能导致写热点问题,成为性能瓶颈。
- LongAdder原理: 通过分散写热点,将单一的热点变量分散到多个变量上,从而减少竞争,提高并发性能。
-
-
- 数据分片与写热点分散
-
-
- 数据分片: 将要处理的数据或请求分成多份,并行处理,是解决写热点问题的重要策略。
- 应用实例:
-
-
-
-
- 数据库的分库分表。
- ES的分布式索引。
- ConcurrentHashMap的分段锁。
-
-
- LongAdder与写热点分散 01:00:42
-
- LongAdder的设计思路
- LongAdder的内部结构
- base变量的作用
- Cell数组的作用
- LongAdder的add方法
- LongAdder与AtomicInteger的区别
- LongAdder的sum方法
-
-
- sum方法: LongAdder的sum方法用于获取当前累加的总和。该方法会将base变量和所有Cell数组中的值进行汇总。但需要注意的是,由于并发操作的存在,sum方法返回的值可能是一个近似值,而不是精确值。
- 强一致性: LongAdder不能保证强一致性,即它提供的值可能不是完全准确的。这是因为它在汇总过程中没有使用锁,从而允许其他线程在汇总过程中继续进行修改操作。因此,在使用LongAdder时,需要考虑到这一点。
-
- 原子操作与事务的原子性 01:05:46
-
- 原子操作的概念
- CAS操作详解
-
-
- CAS定义: CAS(Compare-And-Swap)是一种实现原子操作的方式,通过CPU指令支持,比较并交换内存中的值。
- 操作过程: CAS操作包含三个运算符:一个内存地址V,一个期望的值A和一个新值B。如果地址V上的值等于期望的值A,则将地址V上的值赋为新值B;否则,不做任何操作。
- 循环CAS: 由于CAS操作可能失败,因此在业务层面需要反复重试,直到成功为止,这就是循环CAS。
- 问题: CAS操作可能引入ABA问题、循环时间长开销大以及只能保证一个共享变量的原子操作等问题。
-
-
- Atomic系列原子操作类
-
-
- 类介绍: Java中的Atomic系列原子操作类,如AtomicInteger、AtomicLong等,利用了循环CAS机制来实现原子操作。
- 性能优化: 在JDK 1.8中,为了解决高竞争情况下Atomic性能下降的问题,引入了LongAdder等类,通过分散写热点的方式来提升性能。
-
-
- 事务的原子性
-
-
- 事务特性: 事务具有ACID四大特性,其中原子性(Atomicity)指事务中的多个操作要么全部执行,要么全都不执行。
- 与原子操作的联系: 并发编程中的原子操作和事务的原子性具有相同的内涵和概念,都保证了操作的不可分割性。
-