Java 中的乐观锁与 CAS
在 Java 并发编程中,乐观锁是一种读多写少的情形下的锁实现方式,它假设不会发生冲突,大多数情况下也确实如此。与传统的悲观锁不同,乐观锁不需要在访问共享资源时锁定资源,而是使用版本号或时间戳来判断在操作的过程中心是否有其他线程对资源进行了修改。
Java 提供了一种原子性操作的实现方式,即 Compare And Swap(CAS),它的基本思想是通过比较当前值与预期值,如果相等则进行更新,这样就可以在不使用锁的情况下实现线程安全的操作。
CAS 原理
CAS 是一种原子操作,涉及三个变量: 1. 变量 V,表示要更新的变量。 2. 期望值 A,表示在进行更新操作之前,V 的预期值。 3. 新值 B,表示打算将 V 更新为的值。
CAS 的执行逻辑如下: - 计算机先检查 V 的值是否等于 A。 - 如果相等,则将 V 的值更新为 B;如果不相等,则不进行更新。 - 当这个操作成功时,它会返回 true,否则返回 false。
CAS 操作是原子性的,因此其过程无法被其他线程打断。
Java 中的 CAS 示例
Java 中常用的 AtomicInteger
类是通过 CAS 实现的。下面是一个简单的示例,展示如何使用 AtomicInteger
来实现一个简单的计数器。
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
private AtomicInteger counter = new AtomicInteger(0);
public void increment() {
// 使用 CAS 操作自增计数器
int current;
int next;
do {
current = counter.get(); // 获取当前值
next = current + 1; // 计算下一个值
} while (!counter.compareAndSet(current, next)); // 只在当前值与期望值相等时才进行更新
}
public int getCounter() {
return counter.get();
}
public static void main(String[] args) throws InterruptedException {
CASExample example = new CASExample();
Thread[] threads = new Thread[10];
// 启动10个线程来增加计数器
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
example.increment();
}
});
threads[i].start();
}
// 等待所有线程执行完毕
for (Thread thread : threads) {
thread.join();
}
System.out.println("最终计数器的值是: " + example.getCounter());
}
}
说明
在上面的示例中,我们创建了一个类 CASExample
,其中有一个 AtomicInteger
类型的计数器。increment
方法通过 CAS 来实现安全的自增操作。在 do-while
循环中,我们不断尝试更新计数器,直到成功为止。
在 main
方法中,我们启动了 10 个线程,每个线程自增计数器 1000 次。因为使用了 CAS 方法,所以即使在多线程环境下,最终计数器的值也会是 10000。
总结
乐观锁和 CAS 提供了一种高效的方式来处理并发访问。在读多写少的场景中,使用乐观锁可以减少线程之间的竞争,提升系统性能。尽管 CAS 在理论上简单,但在实践中仍然可能会遇到 ABA 问题,这个问题可以通过引入版本号或使用其他数据结构来解决。在 Java 中,使用原子类 (AtomicInteger
, AtomicReference
等) 是实现乐观锁和 CAS 的最佳实践。