概念
查看JUC包源码时,会发现JUC包中很多操作都是基于CAS基础实现的,CAS是一种无锁算法,在不阻塞程序的情况下,提供多线程安全可靠的数据操作。
所谓的CAS,即是compareAndSwap,比较并替换。CAS操作中,包括三个操作数:内存值V、旧预期值A和新预期值B。CAS执行过程:当内存置V=旧的预期值A时,将内存值V修改为新预期值B,否则放弃修改,重新执行这一过程。
CAS与JUC
在JUC大量使用了CAS操作,例如AtomicBoolean中的compareAndSet方法:
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
Java中CAS基于sun.misc.Unsafe完成,sun.misc.Unsafe提供了大量丰富的API,可以完成操作系统或CPU指令级操作,CAS即是指令级操作,属原子操作。
JDK中sun.misc.Unsafe源码是不可查看的,可以下载OpenJDK查看sun.misc.Unsafe源码。
OpenJDK源码地址:https://download.java.net/openjdk/jdk8/ 或 http://hg.openjdk.java.net/。
下载后解压任一目录下,sun.misc.Unsafe目录是:\openjdk\jdk\src\share\classes\sun\misc\,sun.misc.Unsafe提供的方法基本全为native方法。native方法由C++实现,具体文件目录:\openjdk\hotspot\src\share\vm\prims\,例如下面是针对Int的CAS实现:
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
CAS优缺点
· 优点:在并发不是特别高的情况下,的确可以有效地提高性能。
· 缺点:
· CAS在高并发时,CPU开销较大。
虽然CAS是CPU级指令,但在高并发场景下,大量线程同时循环修改同一变量,CPU使用率会猛然增高。
· CAS只能针对单一变量进行原子操作。
类似volatile,CAS是针对单一变量进行原子操作,而不能像synchronized保证代码块的原子性。
· CAS可能会导致ABA问题。
ABA问题描述:存在变量VAR=100,线程T1从内存取出VAR,同时T2也从内存取出VAR。然后线程T1将VAR修改为200,继续修改为100,线程T1操作完成,执行成功。此时T2发现VAR的值没有改变,且与期望值相等,线程T1也执行成功。这就是ABA问题,从字面来看,一个值有A变为B,继续变为A,原值最终没有变化。
这样的情况,在不同业务下,是会出现不一样的问题,比如银行某账户有存款1000元,某某负责管理,某某因故将400元挪用,存款变为600元,过几天,某某将账户补齐回1000元。在这种业务场景下,ABA问题就是不被允许的。
ABA问题的解决方案是:各种乐观锁的实现通常会用版本戳Version来记录或标记对象,避免并发时出现问题。Java也为我们提供了完整的实现,AtomicStampedReference,通过[V, Integer]标记对象,避免出现ABA问题。
ABA问题复现:
package com.securitit.serialize.aba;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author wangbc.
* @note ABA问题演示.
*/
public class AbaTester {
/**
* AtomicInteger.
*/
private static AtomicInteger atomicInteger = new AtomicInteger(100);
public static void main(String[] args) throws Exception {
Thread atomicThreadFirst = null;
Thread atomicThreadSecond = null;
// 复制ABA线程.
atomicThreadFirst = new Thread() {
public void run() {
atomicInteger.compareAndSet(100, 200);
atomicInteger.compareAndSet(200, 100);
}
};
atomicThreadSecond = new Thread() {
public void run() {
try {
Thread.sleep(3000);
atomicInteger.compareAndSet(100, 300);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
};
// 运行线程.
atomicThreadFirst.start();
atomicThreadSecond.start();
// 线程汇总.
atomicThreadFirst.join();
atomicThreadSecond.join();
// 输出atomicInteger值.
System.out.println("操作后的值:" + atomicInteger.get());
}
}
输出结果
操作后的值:300
根据输出结果可看出,在线程atomicThreadFirst操作后,atomicInteger最终结果是100。线程atomicThreadSecond操作时,由于满足条件,值被设置为300。
ABA解决方案示例:
package com.securitit.serialize.aba;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @author wangbc.
* @note ABA解决方案演示.
*/
public class AbaStampedTester {
/**
* AtomicStampedReference.
*/
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100, 0);
public static void main(String[] args) throws Exception {
Thread stampedThreadFirst = null;
Thread stampedThreadSecond = null;
// 复制ABA线程.
stampedThreadFirst = new Thread() {
public void run() {
atomicStampedReference.compareAndSet(100, 200, atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1);
atomicStampedReference.compareAndSet(200, 100, atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1);
}
};
stampedThreadSecond = new Thread() {
public void run() {
int stamped = 0;
boolean casResult = false;
try {
// 获取初始版本戳.
stamped = atomicStampedReference.getStamp();
System.out.println("CAS执行后的值." + stamped);
Thread.sleep(3000);
casResult = atomicStampedReference.compareAndSet(200, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println("CAS操作是否成功." + casResult);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
};
// 运行线程.
stampedThreadFirst.start();
stampedThreadSecond.start();
}
}
输出结果
CAS执行后的值.0
CAS操作是否成功.false
综上所述,CAS无锁并发算法可以提高程序线程安全性。但是还是那句话‘因地制宜’,选择适合你当下业务需求的解决方案,而不是一味的选择压测等性能最好的。