在Java并发编程中,乐观锁(Optimistic Locking)和比较并交换(Compare-and-Swap, CAS)是非常重要的概念,尤其是在实现无锁或低锁的数据结构和算法时。下面将详细介绍这两个概念。
乐观锁(Optimistic Locking)
乐观锁假设数据在大多数情况下不会发生冲突,因此在读取数据时不加锁,只有在更新数据时才检查数据版本是否发生了变化。如果检测到数据版本已更改,则放弃更新并重新开始整个过程。这种方法减少了锁的竞争,提高了系统的吞吐量。
乐观锁的实现方式:
-
版本号机制:为数据添加一个版本号字段,在读取数据时记录版本号,在更新数据时先检查版本号是否发生变化。如果发生变化,则更新失败。
-
CAS操作:使用比较并交换(CAS)操作来实现乐观锁。CAS是一种原子操作,它尝试用期望的新值替换旧值,只有在值没有被其他线程修改的情况下才会成功。
比较并交换(Compare-and-Swap, CAS)
CAS是一种特殊的原子更新操作,它包含三个参数:存储位置(V)、预期原值(A)和新值(B)。如果存储位置的值与预期原值相匹配,则以原子方式将该位置的值设置为新值。否则,操作失败,不会有任何动作发生。
CAS的特点:
- 原子性:CAS是一个不可分割的操作,要么成功要么失败。
- 无锁:CAS不需要获取锁就可以完成,减少了锁带来的上下文切换和阻塞等问题。
- 非阻塞:如果CAS失败,通常会通过循环的方式重试,直到成功为止。
CAS的Java实现
Java提供了 AtomicInteger
类,它是基于CAS操作的。AtomicInteger
使用 Unsafe
类中的 compareAndSwapInt
方法实现CAS。
示例
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
public static void main(String[] args) {
AtomicInteger counter = new AtomicInteger(0);
// 尝试将counter从0更新为5
boolean success = counter.compareAndSet(0, 5);
if (success) {
System.out.println("Update successful: " + counter.get());
} else {
System.out.println("Update failed.");
}
// 尝试将counter从5更新为10
success = counter.compareAndSet(5, 10);
if (success) {
System.out.println("Update successful: " + counter.get());
} else {
System.out.println("Update failed.");
}
// 尝试将counter从6更新为15(失败)
success = counter.compareAndSet(6, 15);
if (success) {
System.out.println("Update successful: " + counter.get());
} else {
System.out.println("Update failed.");
}
}
}
在这个例子中,我们创建了一个 AtomicInteger
对象 counter
,并尝试使用 compareAndSet
方法更新它的值。如果当前值与预期值相匹配,则更新成功;否则,更新失败。
CAS的局限性
尽管CAS提供了原子性更新和无锁的优点,但它也有几个局限性:
-
ABA问题:如果一个值被更新为另一个值后又变回原来的值,那么CAS操作会误认为没有发生改变。这个问题可以通过使用版本号或者带有时间戳的引用等方法来解决。
-
循环时间开销:如果CAS操作频繁失败,会导致大量的循环重试,从而增加CPU的负担。
-
只能保证一个共享变量的原子性:当有多个共享变量需要原子性操作时,单个CAS操作无法保证整体的原子性。
综上所述,乐观锁和CAS操作在Java并发编程中扮演着重要角色,尤其适用于那些希望减少锁竞争的应用场景。然而,开发者需要充分了解这些技术的局限性,并采取适当措施来克服这些问题。