cas全称Compare and Swap,是程序实现多线程同步的主要方式之一。
简单介绍
下面这段代码中,我们想实现一个计数器,如果像这样实现,在多线程中就会有问题。
index = index + 1;
其实这行代码可以拆成两部分:
1.首先是=右面的运算部分,我们得出了一个值。
2.第二步我们把这个值赋给了index,但这两部并不是一个原子操作,其它线程是可以插到这两步中间执行的,所以当我们赋值时这个index可能已经被别人修改过很多回了。
这种情况下怎么办呢?cpu层面给我们提供了一个功能,就是cas。
大体可以理解为下面这种形式,我们想修改index这个值,如果当前index=1,我们就把它改成2。如果不是就报错,修改失败。
cas(index, 1, 2);
ABA问题
但是上面这种简单实现可能会面临一个问题。
其实我们拿到旧值1这一步和cas操作并不是一个原子的。所以在我们拿到1时,可能其它线程已经给他改了好几次,但最后又改成了1,这时我们拿着我们的1做cas操作,还是可以成功的。这就是常说的ABA问题。
有些时候我们是可以接受ABA问题的,但有些时候又不行。所以CPU对cas的支持已经由比较值,改成了比较版本号。
使用问题
cas有些类似乐观锁的机制,我们每次都乐观的认为可以修改成功,并做出尝试。但他就会存在乐观锁常见的问题。
还是以计数器场景举例。
int current = index; // 我们先获取当前计数
cas(index, current, current+1); // 我们尝试给index+1,但尝试失败了。这时一般都会重试,或者叫自旋
current = index; // 我们重新获取当前计数
cas(index, current, current+1); // 很遗憾又失败了,因为竞争很激烈。
....
就这样在高并发的情况下,cas可能要重试很多次才能成功。所以在极高并发情况下慎用乐观锁。大家可能会疑惑直接使用cas(index, index, index+1)这样是不是就不会有获取数据这步了,其实还是一样的,cpu的每个核都有一块自己的高速缓存,所以肯定会出现数据读取赋值多份这种情况的。
java基于cas的应用
原子工具类
java.util.concurrent.atomic包下的类都是通过cas实现原子操作的。api比较简单,这里就不细说了。
aqs
就是java.util.concurrent.locks.AbstractQueuedSynchronizer这个类,这个类是juc(java.util.concurrent)包下各种并发工具类的框架。它制定了一个并发工具类实现的规范。
aqs字面意思就是通过队列实现的同步器。
aqs有两个比较关键的点,状态和队列。
1.状态
状态是aqs给所有实现类的一个标记,由实现类负责修改并判断是否通过,通过则执行通过逻辑。失败则由aqs把当前线程放到aqs的队列中并阻塞。
2.队列
aqs的队列是由aqs管理的,竞争失败线程的一个队列。实现类无需关心队列中线程的调度,调度由aqs负责。aqs队列分两种,公平和非公平。
a.公平队列,每次不能直接竞争state字段,需要先加入队列,等待aqs调度,性能较差。
b.非公平队列,进入队列前尝试竞争一下state字段,失败加入队列,由于无竞争下无需进入队列,所以竞争不大时,性能较好。
实现类具体需要如何做呢?
1.实现tryAcquire方法。这个方法在竞争时由aqs负责调用,具体的内容一般就是通过cas方式尝试修改state,成功返回true。失败返回false,aqs会给加入队列。
2.实现tryRelease方法。这个方法在释放竞争资源时,由aqs负责调用,具体的内容一般就是通过cas修改state,标记资源已释放。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。