CAS全称Compare And Swap(比较与交换),是一种无锁编程算法,能够完全避免锁竞争带来的体统开销问题,也能够避免CPU在多个线程之间频繁切换和调度带来的开销。从某种程度上说,CAS比加锁机制具有更优的性能。
CAS能够在不阻塞的前提下,以原子性的方式来更新共享变量的数据,也就是在更新变量的数据时,能够保证现成的安全性。CAS算法一般会涉及3个操作数据,分别为:
Unsafe类是java中实现CAS操作的底层核心类,提供了硬件级别的原子性操作,在Unsafe类中,提供了大量的native方法,通过JNI的方式调用JVM底层C和C++实现的函数。在java中的java.util.concurrent.atomic包下的原子类,底层都是基于Unsafe类实现的。
使用Unsafe类获取UnsafeTest类中的静态变量staticNmae和成员变量memberVariable的偏移量,然后通过Unsafe类中的putObject()方法直接修改staticName的值,通过compareAndSwapObject()方法修改memberVariable的值
package com.lifly;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
/**
* @author lifly
* @description
* @date 2023-04-06 22:06
* @versoin 1.0.0
**/
public class UnsafeTest {
private static final Unsafe unsafe = getUnsafe();
private static long staticNameOffset = 0;
private static long memberVariableOffset = 0;
private static String staticName = "lifly_001";
private String memberVariable = "lifly_001";
static {
try {
staticNameOffset = unsafe.staticFieldOffset(UnsafeTest.class.getDeclaredField("staticName"));
memberVariableOffset = unsafe.objectFieldOffset(UnsafeTest.class.getDeclaredField("memberVariable"));
}catch (NoSuchFieldException e){
e.printStackTrace();
}
}
public static void main(String[] args) {
UnsafeTest unsafeTest = new UnsafeTest();
System.out.println("修改前的值如下:");
System.out.println("staticName"+staticName+",memberVariableOffset"+unsafeTest.memberVariable);
unsafe.putObject(UnsafeTest.class,staticNameOffset,"lifly_static");
unsafe.compareAndSwapObject(unsafeTest,memberVariableOffset,"lifly_001","lifly_variable");
System.out.println("修改后的值如下:");
System.out.println("staticName"+staticName+",memberVariableOffset"+unsafeTest.memberVariable);
}
private static Unsafe getUnsafe(){
Unsafe unsafe = null;
try {
Field singleOneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
singleOneInstanceField.setAccessible(true);
unsafe = (Unsafe) singleOneInstanceField.get(null);
}catch (Exception e){
e.printStackTrace();
}
return unsafe;
}
}
在上述代码中,首先通过getUnsafe()方法获取Unsafe实例对象,然后定义了两个long类型的静态变量staticNameOffset和memberVariableOffset,分别表示静态变量staticName和成员变量memberVariable的偏移量。
接下来,定义了一个String类型的静态变量staticName,初始值为lifly_001,定义一个String类型的成员变量memberVariable,初始值为lifly_001。
然后在静态代码块中调用Unsafe类中的putObject()方法修改静态变量staticName的值,调用Unsafe类的compareAndSwapObject()方法修改成员变量memberVariable的值。预期的结果是将静态变量staticName的值修改为lifly_static,将成员变量memberVariable的值修改为lifly_variable,如图所示,结果也符合预期。
设置20个线程并行运行,每个线程通过都通过CAS自旋的方式对一个共享变量的数据进行自增操作,每个线程运行的次数为500次,最终得出的结果为10000
package com.lifly;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch;
import java.util.stream.IntStream;
/**
* @author lifly
* @description
* @date 2023-04-08 13:59
* @versoin 1.0.0
**/
public class CasCountIncrement {
/**
* 获取unsafe对象
*/
private static final Unsafe unsafe = getUnsafe();
/**
* 现成的数量
*/
private static final int THREAD_COUNT = 20;
/**
* 每个线程运行的次数
*/
private static final int EXECUTE_COUNT_EVERY_THREDA = 500;
/**
* 自增的count值
*/
private volatile int count = 0;
/**
* count的偏移量
*/
private static long countOffset;
/**
* 在静态代码块中通过Unsafe类的objectFieldOffset()方法获取count变量的偏移量。将其赋值给countOffset静态变量
*/
static {
try {
countOffset = unsafe.objectFieldOffset(CasCountIncrement.class.getDeclaredField("count"));
}catch (NoSuchFieldException e){
e.printStackTrace();
}
}
/**
* 以CAS的方式对count值进行自增操作
*/
public void incrementCountByCas(){
//将count的值赋值给oldCount
int oldCount = 0;
do {
oldCount = count;
}while (!unsafe.compareAndSwapInt(this,countOffset,oldCount,oldCount+1));
}
/**
* 获取Unsafe实例对象
* @return unsafe
*/
private static Unsafe getUnsafe() {
Unsafe unsafe = null;
try {
Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
singleoneInstanceField.setAccessible(true);
unsafe = (Unsafe) singleoneInstanceField.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}
return unsafe;
}
/**
* 创建main方法,循环20次,在每次循环中创建一个线程,每个线程中调用incrementCountByCas方法,同时在每个线程执行完毕,
* 都调用CountDownLatch的countDown方法使计数器减1.
* 然后再main方法中调用countDownLatch类的await方法阻塞main线程,直到CountDownLatch中的计数器减为0,才继续执行
* 最后,打印count的最终结果数据
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
CasCountIncrement casCountIncrement = new CasCountIncrement();
CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
IntStream.range(0,THREAD_COUNT).forEach((i)->{
new Thread(()->{
IntStream.range(0,EXECUTE_COUNT_EVERY_THREDA).forEach((j)->{
casCountIncrement.incrementCountByCas();
});
countDownLatch.countDown();
}).start();
});
countDownLatch.await();
System.out.println("count的最终结果为:"+casCountIncrement.count);
}
}
可以看到,count的最终结果为10000,与程序的预期结果符合。
ABA问题简单来说就是一个变量的初始值为A,被修改为B,然后再次被修改为A了。在使用CAS算法进行检测时,无法检测出A的值是否经历过被修改为B,又再次被修改为A的过程。
由上图可以看出,当线程A准备调用CAS(1,2)将变量V的值由1修改为2时,CPU发生了线程切换,切换线程B上,线程B在执行业务逻辑的过程中,调用CAS(2,1)将变量V的值由1修改为了2,又调用CAS(2,1)将变量V的值由2修改为了1.然后CPU又发生了线程切换切换到了线程A上,执行CAS(1,2)将变量V的值由1修改为2.虽然线程A的CAS操作能够执行成功,但是期间线程B已经修改了变量V的值了,从而造成了ABA问题。
ABA问题最典型的解决方案就是使用版本号问题。具体的操作方法就是在每次修改数据时都携带一个版本号,只有当该版本号与数据的版本号一致时,才能执行数据的修改,否则修改失败。因为操作的时候携带了版本号,而版本号在每次修改时都会增加,并且只会增加不会减少,所以可以有效的避免ABA问题。
在java.util.concurrent.atomic包下提供了AtomicStampedReference类和AtomicMarkableReference类以解决ABA问题。
AtomicStampedReference类在CAS的基础上增加了一个类似于版本号的时间戳,可以将这个时间戳作为版本号来防止ABA问题,例如AtomicStampedReference类中的warkCompareAndSet()方法和compareAndSet(),AtomicStampedReference类中的warkCompareAndSet()方法和compareAndSet()反复噶的源码如下:
public boolean weakCompareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
return compareAndSet(expectedReference, newReference,
expectedStamp, newStamp);
}
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
从源码中可以看出,AtomicStampedReference类中的weakCompareAndSet()方法内部调用的是compareAndSet()方法实现的。compareAndSet()方法的4个参数如下:
public boolean weakCompareAndSet(V expectedReference,
V newReference,
boolean expectedMark,
boolean newMark) {
return compareAndSet(expectedReference, newReference,
expectedMark, newMark);
}
public boolean compareAndSet(V expectedReference,
V newReference,
boolean expectedMark,
boolean newMark) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedMark == current.mark &&
((newReference == current.reference &&
newMark == current.mark) ||
casPair(current, Pair.of(newReference, newMark)));
}
从源码中可以看出,在AtomicMarkableReference类的weakCompareAndSet()方法compareAndSet()方法的实现中,增加了boolean类型的参数,只判断对象是否被修改过。