ABA 问题 是指使用 CAS 操作进行无锁编程时可能遇到的一种问题。其发生场景如下:
线程 A 读取内存位置 M 的值为 A。
线程 B 将 M 的值修改为 B,然后再将 M 的值修改回 A。
线程 A 再次读取 M 的值为 A。
线程 A 使用 CAS 操作将 M 的值从 A 更新为新值。
尽管线程 A 读取到的值仍然是 A,但它在执行 CAS 操作时仍然会成功,因为它比较的值和期望的值都是 A。然而,实际上 M 的值已经被线程 B 修改过,可能会导致数据不一致问题。
CAS (Compare and Swap) 是一种原子操作,用于在多线程环境中安全地更新变量。其基本原理是:
比较内存中某个位置的值 (期望值)。
如果内存中的值与期望值相等,则将内存中的值更新为新值 (新值)。
如果内存中的值与期望值不相等,则不做任何操作。
CAS 操作通常由处理器指令实现,例如 x86 架构中的 cmpxchg 指令。 由于 CAS 指令是原子操作,它保证了在执行 CAS 操作时,不会有其他线程干扰,从而避免了数据竞争和不一致问题。
共享锁 (Shared Lock):
也称为读锁或共享锁。允许多个线程同时获取锁,并进入临界区执行代码。适用于读多写少的场景,允许多个线程并发读取共享资源,而不会相互干扰。Java 的 ReentrantReadWriteLock 提供了共享锁的实现,允许多个线程同时读取数据,但只有一个线程可以写入数据。
独占锁 (Exclusive Lock):
也称为写锁或独占锁。只允许一个线程获取锁,并进入临界区执行代码。适用于写多读少的场景,确保同一时刻只有一个线程可以修改共享资源,避免数据冲突。Java 的 ReentrantLock 和 synchronized 关键字都提供了独占锁的实现。
可中断锁:
允许线程在等待锁的过程中被中断。这意味着线程可以通过调用 interrupt() 方法来停止等待并执行其他任务。
Java 的 ReentrantLock 是可中断锁的典型例子。它提供了 lockInterruptibly() 方法,该方法会阻塞当前线程直到获取到锁,但如果线程在等待过程中被中断,它会抛出 InterruptedException 并立即返回。
不可中断锁:
不允许线程在等待锁的过程中被中断。线程只能等待直到获取到锁或者永远阻塞下去。
Java 的 synchronized 关键字是不可中断锁的典型例子。一旦线程尝试获取 synchronized 锁,它就会一直等待直到锁被释放,即使其他线程调用了 interrupt() 方法。
公平锁 (Fair Lock):
特点:
保证线程获取锁的顺序与请求锁的顺序相同,即先请求的线程先获取锁。
避免了线程“饥饿”,每个线程都有公平的机会获取锁。
性能开销相对较高,因为需要维护队列,并进行线程之间的比较和竞争。
优点:
缺点: