Java并发的CAS原理与ABA问题的讲解
什么是CAS
CAS,即“Compare and Swap”,是指在计算机硬件中用于实现多线程同步的原子指令。CAS 包含了三个操作数:内存位置 V,旧的预期值 A,要修改的新值 B。当且仅当 V 的值等于 A 时,才将 V 的值更新为 B,否则什么也不做。整个比较并替换过程是“原子”的。
在Java中,CAS是通过sun.misc.Unsafe的方法实现。由于Unsafe是JDK的内部API,因此建议不要随意使用Unsafe。而是通过原子类AtomicInteger、AtomicLong、AtomicReference等封装好的接口进行使用。
CAS的应用
CAS的应用范围很广,比如无锁并发访问、线程安全的计数器、原子操作类等。下面通过一个简单的无锁并发访问的案例来说明CAS的用法:
import java.util.concurrent.atomic.AtomicInteger;
public class CASApplication {
private AtomicInteger count = new AtomicInteger(0);
public int inc() {
for (;;) {
int current = count.get();
int next = current + 1;
if (count.compareAndSet(current, next)) {
return next;
}
}
}
}
代码中使用AtomicInteger来进行操作,而AtomicInteger内部就是通过CAS来实现的。当多个线程并发调用inc()方法时,如果修改的值已经被其他线程修改,那么CAS会失败,当前线程会重新读取最新的值并继续尝试修改,直到修改成功为止。
ABA问题
CAS看起来很完美,但实际上存在一个比较严重的问题,即ABA问题。通常来说,在CAS执行过程中,如果当前内存位置的值A被修改为值B,但又被修改回值A,那么CAS往往会认为A没有被修改过。下面通过一个简单的例子来演示ABA问题:
import java.util.concurrent.atomic.AtomicReference;
public class ABAProblem {
private static AtomicReference<String> name = new AtomicReference<>("jack");
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
String s = name.get();
System.out.println(Thread.currentThread().getName() + " read value: " + s);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
name.compareAndSet(s, "tom");
System.out.println(Thread.currentThread().getName() + " update value: " + name.get());
});
Thread t2 = new Thread(() -> {
String s = name.get();
System.out.println(Thread.currentThread().getName() + " read value: " + s);
name.compareAndSet(s, "jack");
System.out.println(Thread.currentThread().getName() + " update value: " + name.get());
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
在以上代码中,线程t1先读取name的值为"jack",然后将其修改为"tom"。此时线程t2也读取name的值为"jack",并将其修改回"jack"。最后结果是name的值被修改为了"tom",但t2的操作却被CAS认为成功了。
为了解决ABA问题,在Java中可以使用带版本号的原子操作类,比如AtomicStampedReference。方法中除了需要传递要修改的值之外,还需要传递一个版本号。只有当当前版本号和期望的版本号一致时,才会进行修改操作,并且每次修改都会使版本号加1。
上述例子如果使用AtomicStampedReference,可以修改为:
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABAProblem {
private static AtomicStampedReference<String> name =
new AtomicStampedReference<>("jack", 0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
int stamp = name.getStamp();
String s = name.getReference();
System.out.println(Thread.currentThread().getName() + " read value: " + s);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
name.compareAndSet(s, "tom", stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + " update value: " + name.getReference());
});
Thread t2 = new Thread(() -> {
int stamp = name.getStamp();
String s = name.getReference();
System.out.println(Thread.currentThread().getName() + " read value: " + s);
name.compareAndSet(s, "jack", stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + " update value: " + name.getReference());
});
t1.start();
t2.start();
t1.join();
t2.join();
}
}
总结
CAS作为Java多线程编程中的重要基础,很多原子操作类都是基于CAS实现。在使用CAS时,需要注意ABA问题,如果存在这个问题,可以考虑使用带版本号的原子类来解决。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java并发的CAS原理与ABA问题的讲解 - Python技术站