CAS 完全解析

  概念

  查看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优缺点

  · 优点:在并发不是特别高的情况下,的确可以有效地提高性能。

扫描二维码关注公众号,回复: 11363174 查看本文章

  · 缺点:

  · 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无锁并发算法可以提高程序线程安全性。但是还是那句话‘因地制宜’,选择适合你当下业务需求的解决方案,而不是一味的选择压测等性能最好的。

猜你喜欢

转载自blog.csdn.net/securitit/article/details/106753310
Cas