什么是CAS
CAS(Compare And Swap,比较并交换),通常指的是这样一种原子操作:针对一个变量,首
先比较它的内存值与某个期望值是否相同,如果相同,就给它赋一个新值。
CAS 的逻辑用伪代码描述如下:
if (value == expectedValue) {
value = newValue;
}
以上伪代码描述了一个由比较和赋值两阶段组成的复合操作,CAS 可以看作是它们合并后的整体
——一个不可分割的原子操作,并且其原子性是直接在硬件层面得到保障的。
CAS可以看做是乐观锁(对比数据库的悲观、乐观锁)的一种实现方式,Java原子类中的递增操
作就通过CAS自旋实现的。
CAS是一种无锁算法,在不使用锁(没有线程被阻塞、上下文切换)的情况下实现多线程之间的变量同步。因此优先考虑使用CAS实现功能。
代码演示
10个线程同时,做100000以内的累加,如何实现? 有几种方法?不同实现方式效率对比:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.ReentrantLock;
import com.test.jucdemo.lock.CASLock;
/**
* @author
*/
public class Test {
// 保证可见性
private volatile static int sum = 0;
static Object object = "";
static ReentrantLock lock = new ReentrantLock();
static CASLock casLock = new CASLock();
/**
* // 10个线程同时,做100000以内的累加,如何实现? 有几种方法?
*
* @param args
*/
public static void main(String[] args) throws InterruptedException {
test1();
test2();
test3();
test4();
}
public static void test1() throws InterruptedException {
long start = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
for (int j = 0; j < 100000; j++) {
sum++;
}
countDownLatch.countDown();
});
thread.start();
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println(String.format("volatile sum : %s, 耗时:%s", sum, end - start));
sum = 0;
}
public static void test2() throws InterruptedException {
long start = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(10);
Object object = "";
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
for (int j = 0; j < 100000; j++) {
synchronized (object) {
sum++;
}
}
countDownLatch.countDown();
});
thread.start();
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println(String.format("synchronized sum : %s, 耗时:%s", sum, end - start));
sum = 0;
}
public static void test3() throws InterruptedException {
long start = System.currentTimeMillis();
ReentrantLock lock = new ReentrantLock();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
lock.lock();
try {
for (int j = 0; j < 100000; j++) {
sum++;
}
} finally {
countDownLatch.countDown();
lock.unlock();
}
});
thread.start();
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println(String.format("ReentrantLock sum : %s, 耗时:%s", sum, end - start));
sum = 0;
}
public static void test4() throws InterruptedException {
long start = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
// 自旋
for (; ; ) {
//state=0
// System.out.println(casLock.getState());
if (casLock.getState() == 0 && casLock.cas()) {
try {
for (int j = 0; j < 100000; j++) {
sum++;
}
// System.out.println(casLock.getState());
} finally {
// state=0
casLock.setState(0);
}
break;
}
}
countDownLatch.countDown();
});
thread.start();
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println(String.format("casLock sum : %s, 耗时:%s", sum, end - start));
sum = 0;
}
}
CASLock代码在别的目录下面,没有粘贴,此处待优化
结果展示:
CAS缺陷
CAS 虽然高效地解决了原子操作,但是还是存在一些缺陷的,主要表现在三个方面:
- 自旋(CAS操作一般放在自旋中使用,automic包中的实现已经包含的自旋的逻辑) 长时间地不成功,则会给 CPU 带来非常大的开销
解决方案:LongAdder,LongAccumulator 样例代码如下,具体实现原理忽略
package com.test.jucdemo.atomic;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.LongAdder;
/**
* @author
*/
public class LongAdderTest {
public static void main(String[] args) {
testAtomicLongVSLongAdder(10, 10000);
System.out.println("==================");
testAtomicLongVSLongAdder(10, 200000);
System.out.println("==================");
testAtomicLongVSLongAdder(100, 200000);
}
static void testAtomicLongVSLongAdder(final int threadCount, final int times) {
try {
System.out.println("条件>>>>>>线程数:" + threadCount + ", 单线程操作计数" + times);
long start = System.currentTimeMillis();
testLongAdder(threadCount, times);
long end = System.currentTimeMillis() - start;
System.out.println("结果>>>>>>LongAdder方式增加计数" + (threadCount * times) + "次,共计耗时:" + end);
long start2 = System.currentTimeMillis();
testAtomicLong(threadCount, times);
long end2 = System.currentTimeMillis() - start2;
// System.out.println("条件>>>>>>线程数:" + threadCount + ", 单线程操作计数" + times);
System.out.println("结果>>>>>>AtomicLong方式增加计数" + (threadCount * times) + "次,共计耗时:" + end2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static void testAtomicLong(final int threadCount, final int times) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
AtomicLong atomicLong = new AtomicLong();
for (int i = 0; i < threadCount; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < times; j++) {
atomicLong.incrementAndGet();
}
countDownLatch.countDown();
}
}, "my-thread" + i).start();
}
countDownLatch.await();
}
static void testLongAdder(final int threadCount, final int times) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(threadCount);
LongAdder longAdder = new LongAdder();
for (int i = 0; i < threadCount; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < times; j++) {
longAdder.add(1);
}
countDownLatch.countDown();
}
}, "my-thread" + i).start();
}
countDownLatch.await();
}
}
自旋不成功,是不是也相当于阻塞? 自旋失败造成的CPU空转和阻塞、用户态至内核态的切换两者怎么选择?
并发场景下优化,优先选择CAS+自旋解决方案,对比的案例也可以说明
automic包中的实现已经包含的自旋的逻辑
- 只能保证一个共享变量原子操作
- ABA 问题 AtomicStampedReference
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.LockSupport;
import lombok.extern.slf4j.Slf4j;
/**
* @author Fox
*/
@Slf4j
public class AtomicStampedReferenceTest {
public static void main(String[] args) {
// 定义AtomicStampedReference Pair.reference值为1, Pair.stamp为1
AtomicStampedReference atomicStampedReference = new AtomicStampedReference(1,1);
new Thread(()->{
int[] stampHolder = new int[1];
int value = (int) atomicStampedReference.get(stampHolder);
int stamp = stampHolder[0];
log.debug("Thread1 read value: " + value + ", stamp: " + stamp);
// 阻塞1s
LockSupport.parkNanos(1000000000L);
// Thread1通过CAS修改value值为3 stamp是版本,每次修改可以通过+1保证版本唯一性
if (atomicStampedReference.compareAndSet(value, 3,stamp,stamp+1)) {
log.debug("Thread1 update from " + value + " to 3");
} else {
log.debug("Thread1 update fail!");
}
boolean b = atomicStampedReference.attemptStamp(value, 3);
if (b) {
log.debug("Thread1 update " + " to 3");
}
// atomicStampedReference.
},"Thread1").start();
new Thread(()->{
int[] stampHolder = new int[1];
int value = (int)atomicStampedReference.get(stampHolder);
int stamp = stampHolder[0];
log.debug("Thread2 read value: " + value+ ", stamp: " + stamp);
// Thread2通过CAS修改value值为2
if (atomicStampedReference.compareAndSet(value, 2,stamp,stamp+1)) {
log.debug("Thread2 update from " + value + " to 2");
// do something
value = (int) atomicStampedReference.get(stampHolder);
stamp = stampHolder[0];
log.debug("Thread2 read value: " + value+ ", stamp: " + stamp);
// Thread2通过CAS修改value值为1
if (atomicStampedReference.compareAndSet(value, 1,stamp,stamp+1)) {
log.debug("Thread2 update from " + value + " to 1");
}
log.debug("Thread2 update stamp: " + atomicStampedReference.getStamp());
}
},"Thread2").start();
}
}
juc.atomic
具体使用很简单,分类的话可以到 juc.atomic包下面看一下即可。
参考链接:从 synchronized 到 CAS 和 AQS - 彻底弄懂 Java 各种并发锁_weixin_34326558的博客-CSDN博客
Java中CAS原理详解 - BarryW - 博客园