「这是我参与2022首次更文挑战的第5天,活动详情查看:2022首次更文挑战」。
cas和aqs是java并发编程的基础,我们在开发过程中可能很少有机会直接使用到java提供的这两个算法或框架,但是这两种算法确实构成java并发编程的基石。
像我们熟悉的ReentrantLock,信号量,栅栏,CountDownLatch等JUC包下的并发工具类都是由AQS框架实现的。原子类,ConcurrentHashMap等工具包括AQS的实现都是依赖于CAS的。
所以学习CAS和AQS对于学习java并发编程的原理有着重要的意义。
CAS(Compare And Swap)
CAS算法理解:
cas即比较再交换,是一种无锁算法。CAS有三个操作数,内存值V,旧的预期值A,要修改的新值B,当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做
cas可以实现原子性操作
为了理解cas操作,我们先来看一个经典的多线程操作 i++ 的场景
我们都知道 i操作在计算机底层,是由多条cpu指令来实现的,不是原子操作,想要保证线程安全必须对 i 操作加锁,代码如下:
public class SynchronizedIncrDemo {
private static final int n = 100;
public static void main(String[] args) throws InterruptedException {
//保证子线程执行完后,主线程再打印storage.i的值
CountDownLatch cd = new CountDownLatch(n);
//创建内部类Storage对象
CASDemo.Storage storage = new CASDemo.Storage();
long t1 = System.currentTimeMillis();
//创建 n 个线程,每个线程内部循环10000 次,进行自增操作
for (int i = 0; i < n; i++) {
new Thread(() -> {
for (int x = 0; x <10000; x++) {
storage.incr();
}
cd.countDown();
}).start();
}
cd.await();
long t2 = System.currentTimeMillis();
System.out.println(storage.i);
System.out.println("cost: " + (t2-t1));
}
static class Storage {
public int i;
//使用 synchronized 加锁实现原子操作
public synchronized void incr() {
i++;
}
}
}
上面这段代码我们已经非常熟悉了,如果我们使用CAS操作应该怎么用代码实现?
如图,是cas操作的流程:
- 先读取当前i 的值记为 A
- 对 i 进行 +1 操作 记为 B
- 再读取 i 的值记为 V
- 比较 A 和 V是否相等
- 相等则更新 i 的值为新值 B
- 不相等则重复上述操作
下面我们用代码来实现
public class CasIncrDemo {
private static final int n = 100;
public static void main(String[] args) throws InterruptedException {
//保证子线程执行完后,主线程再打印storage.i的值
CountDownLatch cd = new CountDownLatch(n);
//创建内部类Storage对象
Storage storage = new Storage();
long t1 = System.currentTimeMillis();
//创建 n 个线程,每个线程内部循环10000 次,进行自增操作
for (int i = 0; i < n; i++) {
new Thread(() -> {
for (int x = 0; x <10000; x++) {
storage.casIncr();
}
cd.countDown();
}).start();
}
cd.await();
long t2 = System.currentTimeMillis();
System.out.println(storage.i);
System.out.println("cost: " + (t2-t1));
}
static class Storage {
public int i;
public void casIncr() {
int v = i;
//自旋直到cas操作成功
while (!compareAndSwap(v, v + 1)) ;
}
/**
* cas 操作
* @param expectedNum 期望值
* @param newNum 新值
* @return
*/
private boolean compareAndSwap(int expectedNum, int newNum) {
synchronized (this) {
if (expectedNum == i) {
i = newNum;
return true;
}
}
return false;
}
}
}
上面的cas操作我们从java 语言层面实现了cas 操作,美中不足的是 compareAndSwap 中的操作 我们必须使用 synchronized 来保证原子操作.