CAS(Compare And Swap,比较并交换),通常指的是这样一种原子操作:针对一个变量,首先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值。
CAS 的逻辑用伪代码描述如下:
if (value == expectedValue) {
value = newValue;
}
以上伪代码描述了一个由比较和赋值两阶段组成的复合操作,CAS 可以看作是它们合并后的整体——一个不可分割的原子操作,并且其原子性是直接在硬件层面得到保障的。
CAS可以看做是乐观锁(对比数据库的悲观、乐观锁)的一种实现方式,Java原子类中的递增操作就通过CAS自旋实现的。
常见的乐观锁实现方式有两种,分别是:1、版本号机制;2、CAS算法。其中,通过版本号机制实现乐观锁是最经典的方法。版本号机制一般是在数据表中加上一个数据库版本号version字段。
/**
* update 80 version:2 where version1 =数据库version2
*UPDATE
* tbl_employee
* SET
* last_name='C',
* email='123@qq.com',
* gender=1,
* age=80,
* modify_date='2022-11-15 07:09:31.18',
* version=2
* WHERE
* id=2
* AND version=1
* AND delete_flag=0
* */
CAS是一种无锁算法,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。
import java.util.concurrent.atomic.AtomicInteger;
//示例
public class VolatileVisibility2 {
public static int i = 0;
public static AtomicInteger integer = new AtomicInteger(0);
public static Object lock = new Object();
public static void increase() {
// synchronized (lock){
// i++;
// }
i++;
integer.incrementAndGet();
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
increase();
}
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5000; i++) {
increase();
}
}
},"t2");
t1.start();
t2.start();
t1.join();
t2.join();
//思考: counter=?
System.out.println("i = "+ i);
System.out.println("integer = "+ integer.get());
}
}
在 Java 中,CAS 操作是由 Unsafe 类提供支持的,该类定义了三种针对不同类型变量的 CAS 操作,如图
它们都是 native 方法,由 Java 虚拟机提供具体实现,这意味着不同的 Java 虚拟机对它们的实现可能会略有不同。
以 compareAndSwapInt 为例,Unsafe 的 compareAndSwapInt 方法接收 4 个参数,分别是:对象实例、内存偏移量、字段期望值、字段新值。该方法会针对指定对象实例中的相应偏移量的字段执行 CAS 操作。
package com.cctv;
import sun.misc.Unsafe;
public class CASTest {
public static void main(String[] args) {
Entity entity = new Entity();
Unsafe unsafe = UnsafeFactory.getUnsafe();
long offset = UnsafeFactory.getFieldOffset(unsafe, Entity.class, "x");
boolean successful;
// 4个参数分别是:对象实例、字段的内存偏移量、字段期望值、字段新值
successful = unsafe.compareAndSwapInt(entity, offset, 0, 3);
System.out.println(successful + "\t" + entity.x);
successful = unsafe.compareAndSwapInt(entity, offset, 3, 5);
System.out.println(successful + "\t" + entity.x);
successful = unsafe.compareAndSwapInt(entity, offset, 3, 8);
System.out.println(successful + "\t" + entity.x);
}
}
class Entity{
int x;
}
package com.cctv;
import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeFactory {
/**
* 获取 Unsafe 对象
*
* @return
*/
public static Unsafe getUnsafe() {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取字段的内存偏移量
*
* @param unsafe
* @param clazz
* @param fieldName
* @return
*/
public static long getFieldOffset(Unsafe unsafe, Class clazz, String fieldName) {
try {
return unsafe.objectFieldOffset(clazz.getDeclaredField(fieldName));
} catch (NoSuchFieldException e) {
throw new Error(e);
}
}
}
测试
针对 entity.x 的 3 次 CAS 操作,分别试图将它从 0 改成 3、从 3 改成 5、从 3 改成 8。执行结果如下:
CAS 虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方面:
CAS + 自旋:
思考:这种CAS失败自旋的操作存在什么问题?
AtomicInteger atomicInteger = new AtomicInteger(1);
atomicInteger.incrementAndGet();
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}