Syncorized和cas存在什么差别,各个方面详细讲解,性能上详细讲解,同时从操作系统的角度详细介绍
synchronized 和 Compare-and-Swap (CAS) 是两种在并发编程中解决线程安全问题的核心机制,它们在实现方式、性能特点和与操作系统的交互方面存在显著差异。
synchronized
1. 定义与机制:
synchronized 是 Java 语言的关键字,用于实现同步,它提供了一种悲观锁(Pessimistic Locking)的机制。当一个线程进入 synchronized 代码块或方法时,它会尝试获取对象的监视器锁(monitor lock)。如果锁已被其他线程持有,当前线程会被阻塞,进入等待状态,直到锁被释放。synchronized 保证了同一时刻只有一个线程可以执行临界区(critical section)的代码,从而确保了数据的一致性。synchronized 确保了互斥性、进程、和有界等待(无饥饿)等关键部分问题的三个基本规则。
2. 锁的粒度与类型:
synchronized 作用于对象或方法:
* 同步方法: 锁定当前实例对象。
* 静态同步方法: 锁定当前类的 Class 对象。
* 同步代码块: 锁定括号中指定的对象。
synchronized 提供的锁是独占锁(Exclusive Lock)且是非公平锁(Unfair Lock),这意味着线程获取锁的顺序不是严格按照请求的先后顺序。
3. 性能特点:
* 开销: 传统上,synchronized 被认为是“重量级锁”,因为它涉及操作系统层面的上下文切换,当线程被阻塞时,需要操作系统介入进行线程的挂起和唤醒,这会带来较大的性能开销。
* 现代JVM优化: 现代JVM对 synchronized 进行了大量优化,包括偏向锁(Biased Locking)、轻量级锁(Lightweight Locking)和自适应自旋(Adaptive Spinning)等。在低竞争情况下,synchronized 的性能已大大提高,甚至可以接近非阻塞同步的性能。
* 高竞争情况: 在高竞争场景下,当自旋等待的开销超过线程挂起和唤醒的开销时,synchronized 可能会表现出更好的性能,因为它会选择将等待的线程放入队列并挂起,而不是让它们持续占用CPU自旋。
4. 操作系统角度:
* 内核态介入: 当 synchronized 锁发生竞争,并且线程需要被阻塞时,操作系统会介入。线程会从用户态切换到内核态,被挂起并放入等待队列。这种上下文切换是昂贵的,因为它涉及到保存和恢复CPU寄存器、程序计数器等信息。
* 调度: 操作系统负责管理这些等待的线程,并在锁释放时,根据调度策略唤醒其中一个线程使其重新获得CPU执行权。
* 同步硬件: 操作系统通过利用底层硬件提供的原子操作(如禁用中断、Test-and-Set指令或Compare-and-Swap指令)来确保互斥锁的原子性,从而实现 synchronized 这样的同步机制。
* 避免竞态条件: 进程同步机制(如 synchronized)是操作系统管理并发进程、确保共享资源访问有序、防止竞态条件和数据不一致的关键。
CAS (Compare-and-Swap)
1. 定义与机制:
CAS(Compare-and-Swap,比较并交换)是一种乐观锁(Optimistic Locking)的实现方式,它是一种硬件指令级别的原子操作。CAS 操作包含三个参数:内存位置 V、预期旧值 A 和新值 B。CAS 操作的逻辑是:如果内存位置 V 的当前值与预期旧值 A 相匹配,那么将 V 的值更新为新值 B;否则,不做任何操作。无论更新成功与否,CAS 操作都会返回 V 的旧值。这种机制确保了在并发环境下,只有一个线程能成功执行操作,而其他线程会失败并可以选择重试。Java 中的 java.util.concurrent.atomic 包下的类(如 AtomicInteger、AtomicLong)以及 AbstractQueuedSynchronizer (AQS) 的实现都基于 CAS。
2. 锁的粒度与类型:
* 非阻塞: CAS 是一种非阻塞算法,它不会阻塞线程,而是让线程在操作失败时重试,从而避免了传统锁机制带来的上下文切换开销。
* 原子性: CAS 操作由CPU硬件指令保证其原子性,例如 x86 架构上的 lock cmpxchg 指令,这意味着比较和交换这两个步骤是不可分割的,不会被中断。
3. 性能特点:
* 低竞争情况: 在低竞争(或无竞争)环境下,CAS 的性能通常优于 synchronized,因为它避免了锁的获取、释放以及潜在的线程阻塞和上下文切换开销。
* 高竞争情况: 在高竞争环境下,CAS 可能会导致大量的失败和重试(自旋),这会消耗大量的CPU时间,形成“忙等待”(busy-waiting),即线程在不断地尝试更新但每次都失败。这种情况下,CAS 的性能可能会下降,甚至不如 synchronized。
* 可伸缩性: 非阻塞算法(如基于CAS的算法)通常具有更短的“临界区”,这使得它们在多核系统上具有更好的可伸缩性。
* LongAdder优化: 为了解决高竞争下 CAS 的自旋问题,Java 8 引入了 LongAdder,它通过分段(Striping)的策略,将竞争分散到多个 Cell 上,从而在高并发环境下比 AtomicLong(基于CAS)和 synchronized 表现出更好的性能。
4. 操作系统角度:
* 硬件指令支持: CAS 的核心在于操作系统和CPU提供的底层硬件指令支持,例如 cmpxchg 指令。这些指令允许CPU原子性地执行比较和交换操作,而无需操作系统层面的显式锁机制。
* 内存一致性与缓存: 尽管 CAS 操作是硬件级别的,但它仍然需要访问主内存。在多处理器系统中,CAS 操作会涉及到缓存一致性协议(如MESI协议),以确保所有CPU核心对共享变量的视图是一致的。这部分工作由硬件和操作系统协同完成。
* 减少内核态切换: CAS 的优势在于它尽量避免了线程的阻塞和内核态切换,从而减少了操作系统对同步操作的直接干预。当 CAS 失败时,线程可以立即重试或者执行其他逻辑,而不需要进入等待队列并等待操作系统调度。
* 原子操作基石: CAS 提供了一种构建非阻塞数据结构和算法的底层原子原语。许多高级的并发工具,如 ConcurrentHashMap 中的某些操作以及 java.util.concurrent.locks 包中的锁实现,都利用了 CAS 操作来确保线程安全和提高性能。
总结比较
| 特性 | synchronized | CAS (Compare-and-Swap) |
|---|---|---|
| 机制 | 悲观锁,阻塞式同步 | 乐观锁,非阻塞式同步 |
| 实现 | Java 关键字 | CPU 硬件指令级别的原子操作 |
| 性能 | 低竞争: 经过JVM优化后性能良好 | 低竞争: 性能优异,避免上下文切换 |
| 高竞争: 可能导致线程阻塞和上下文切换,但避免自旋,有时优于CAS | 高竞争: 易产生自旋(忙等待),消耗CPU,可能性能下降 | |
| 锁粒度 | 通常用于代码块或方法,保护较大临界区 | 通常用于对单个共享变量的原子更新 |
| 灵活性 | 较少灵活性,自动管理锁的获取和释放 | 更灵活,可用于实现更复杂的非阻塞算法 |
| 操作系统 | 竞争时涉及OS的线程调度、挂起和唤醒,有内核态切换开销 | 依赖CPU硬件原子指令,尽量避免OS介入,无直接内核态切换 |
| 应用 | 适用于对复杂业务逻辑和较大代码块的同步 | 适用于对单个变量进行原子操作,构建无锁数据结构 |
选择 synchronized 还是 CAS 取决于具体的应用场景和对性能、复杂性的权衡。在竞争不激烈的情况下,或者对代码的简洁性和可理解性要求更高时,synchronized 可能是更好的选择。而在追求极致性能、避免线程阻塞,且能处理好 CAS 失败重试逻辑的场景下,基于 CAS 的原子操作(或更高级的如 LongAdder)会是更优的选择。