文章目录
基本概念
CPU 为了解决并发问题,提供了 CAS 指令(CAS,全称是 Compare And Swap,即"比较并交换")。CAS 指令包含 3个参数:共享变量的内存地址 A、用于比较的值 B 和共享变量的新值 C;并且只有当内存中地址 A 处的值等于 B 时,才能将内存中地址 A 处的值更新为新值 C。作为一条 CPU 指令,CAS 指令本身是能够保证原子性的。
模拟cas操作伪代码:
java
int getAndIncrement() {
int newValue;
int oldVal;
do {
oldVal = value;
newValue = oldVal + 1;
// cas前跟cas后读取的值不一样 重试一遍
} while (oldVal != cas(oldVal, newValue));
return value;
}
synchronized int cas(int expect, int newValue) {
// 读取当前值
int curValue = value;
// 比较目前count值是否==期望值
if (curValue == expect) {
// 如果是,则更新count的值 不相等 说明在执行获取value后 执行cas之前 该值已经被修改了
// 也就是进入cas之前读到的value值跟进入cas之后读的value值不相等
value = newValue;
}
return curValue;
}
利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法,实现原子操作,其它原子操作都是利用类似的特性完成的。
整个JUC包都是建立在CAS之上的,因此对于synchronized
阻塞算法,J.U.C在性能上有了很大的提升。
CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。
常见的CAS操作类
JUC包中的常用原子操作类
java.util.concurrent.atomic
包提供了原子操作类:
AtomicInteger
:Integer
类型的CAS原子操作
AtomicLong
:Long
类型的CAS原子操作
AtomicBoolean
:Boolean
类型的CAS原子操作
AtomicReference
:引用类型的CAS原子操作
以 AtomicInteger
为例子,它提供了支持原子操作的方法,包括
java
int get() //获取一个值
int getAndSet(int newValue) //获取并设置值
int getAndIncrement() //获取并进行+1操作
int incrementAndGet() //进行+1后获取值
int getAndDecrement() //获取并-1操作
int decrementAndGet() //进行-1操作后在获取
以下代码使用原子性操作,可以确保最后输出的结果是100
java
public class Atom_Test {
private static AtomicInteger i = new AtomicInteger(0);
public static void main(String[] args) {
for (int j = 0; j < 100; j++) {
new Thread() {
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
i.getAndIncrement();
}
}.start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println(i);
}
}
CAS操作的优缺点
优点
确保对内存的读-改-写操作都是原子操作执行
缺点
- ABA问题
因为CAS需要在操作值的时候,检查值有没有发生变化,如果没有发生变化 则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它 的值没有发生变化,但是实际上却变化了。
ABA问题的解决思路就是使用版本号。在变量前面 追加上版本号,每次变量更新的时候把版本号加1,那么A→B→A就会变成1A→2B→3A。
从 Java 1.5开始,JDK的Atomic
包里提供了一个类AtomicStampedReference
来解决ABA问题。
java
/**
* Atomically sets the value of both the reference and stamp
* to the given update values if the
* current reference is {@code ==} to the expected reference
* and the current stamp is equal to the expected stamp.
*
* @param expectedReference the expected value of the reference
* @param newReference the new value for the reference
* @param expectedStamp the expected value of the stamp
* @param newStamp the new value for the stamp
* @return {@code true} if successful
*/
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
- 循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销 - 只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循 环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子 性,这时候就需要用到synchronized
。