系列文章目录
文章目录
一、乐观锁
1、CAS
CAS是一种原子操作(不可被中断的操作),用于实现多线程环境下的同步控制,基本逻辑是:
c
boolean compareAndSwap(int expectedValue, int newValue)
比较当前值是否等于expectedValue
如果相等,则将当前值更新为newValue
返回是否成功,true表示更新成功
CAS的作用
CAS是实现无锁和乐观锁的基础,避免了传统锁带来的性能问题,比如线程阻塞,死锁等
2、示例
1、java中的CAS操作是通过sun.misc.unsafe类提供的,这个类提供了底层的内存操作能力
但是unsafe类是一个不公开的API, 因此java提供了更高层的封装,比如AtomicInteger, AtomicLong等
c
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(0);
// 尝试将值从 0 改为 1
boolean success = atomicInteger.compareAndSet(0, 1);
System.out.println("First CAS: " + success); // true
// 再次尝试将值从 0 改为 2(此时实际值是 1)
success = atomicInteger.compareAndSet(0, 2);
System.out.println("Second CAS: " + success); // false
// 使用 getAndIncrement(内部使用 CAS)
int value = atomicInteger.getAndIncrement();
System.out.println("After increment: " + value); // 1
}
}
2、手动实现CAS
c
public class SimpleCAS {
private volatile int value;
public SimpleCAS(int initialValue) {
this.value = initialValue;
}
public synchronized boolean compareAndSet(int expected, int update) {
if (value == expected) {
value = update;
return true;
}
return false;
}
public int getValue() {
return value;
}
public static void main(String[] args) {
SimpleCAS cas = new SimpleCAS(0);
System.out.println(cas.compareAndSet(0, 1)); // true
System.out.println(cas.compareAndSet(0, 2)); // false
System.out.println(cas.getValue()); // 1
}
}
注意上面compareAndSet是用synchronized 实现的,这仍然是锁机制,不是真正的CAS
3、ABA问题以及解决方法
1、什么是ABA问题
假设变量 x 的值是 A,然后被修改为 B,再被改回 A。
如果你使用普通的 CAS(如 AtomicInteger),它会认为 x 的值没有变,但实际上中间经历了变化。
这可能导致错误的逻辑判断。
2、如何ABA
AtomicStampedReference 通过引入一个版本号(stamp),每次修改时都更新这个版本号,从而可以检测到这种变化
c
package practice.cas讲解;
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABACase {
public static void main(String[] args) {
//初始值为 0,版本号为 0
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(0, 0);
//Java 是按值传递的,如果你传一个 int 变量进去,函数内部修改它不会影响外部的变量。
//因此,我们使用一个 int[] 数组来包装 int,这样可以在方法内部修改数组中的值,外部也能看到。
int[] stamp = {0};
// 第一次修改
//如果期望值和版本号都匹配,则修改成功
//如果当前值是0,版本号是0,则将值改为1,版本号改为1
boolean success = ref.compareAndSet(0, 1, 0, 1);
System.out.println("First CAS: " + success); // true
// 第二次修改(ABA 问题)
//当前值是1,版本号是1,尝试将值改回0,版本号改为2
//此时值从 1 变成 0,但之前曾经是 0。这就是 ABA 问题的体现。
success = ref.compareAndSet(1, 0, 1, 2);
System.out.println("Second CAS: " + success); // true
// 第三次修改(使用正确的 stamp)
//当前值是0,版本号是2,尝试将值从0改为3,版本号改为3
//什么情况可以返回false?
//如果当前值不是0,或者版本号不是2,则修改失败,返回false
//因为版本号已经变了,说明中间有其他修改发生过
success = ref.compareAndSet(0, 1, 2, 3);
System.out.println("Third CAS: " + success); // true
}
}
3、多线程示例,重现ABA问题
虽然两个线程都"修改"了值,但最终值还是 0。这说明 AtomicInteger 无法检测到 ABA 问题
c
import java.util.concurrent.atomic.AtomicInteger;
public class ABASample {
public static void main(String[] args) {
AtomicInteger value = new AtomicInteger(0);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
int current = value.get();
if (current == 0) {
// 假设这里有一些操作
value.compareAndSet(0, 1);
System.out.println("T1: Set to 1");
try {
Thread.sleep(10); // 模拟延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
value.compareAndSet(1, 0);
System.out.println("T1: Set back to 0");
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
int current = value.get();
if (current == 0) {
// 假设这里有一些操作
value.compareAndSet(0, 1);
System.out.println("T2: Set to 1");
try {
Thread.sleep(10); // 模拟延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
value.compareAndSet(1, 0);
System.out.println("T2: Set back to 0");
}
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final value: " + value.get());
}
}
每次修改都会更新版本号(stamp),即使值回到原点,也能识别出变化。
c
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABASolution {
public static void main(String[] args) {
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(0, 0);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
int[] stamp = {0};
Integer current = ref.get(stamp);
if (current == 0) {
boolean success = ref.compareAndSet(0, 1, 0, 1);
if (success) {
System.out.println("T1: Set to 1 with stamp 1");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
success = ref.compareAndSet(1, 0, 1, 2);
if (success) {
System.out.println("T1: Set back to 0 with stamp 2");
}
}
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
int[] stamp = {0};
Integer current = ref.get(stamp);
if (current == 0) {
boolean success = ref.compareAndSet(0, 1, 0, 1);
if (success) {
System.out.println("T2: Set to 1 with stamp 1");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
success = ref.compareAndSet(1, 0, 1, 2);
if (success) {
System.out.println("T2: Set back to 0 with stamp 2");
}
}
}
}
});
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
int[] finalStamp = {0};
Integer finalValue = ref.get(finalStamp);
System.out.println("Final value: " + finalValue + ", stamp: " + finalStamp[0]);
}
}