CAS的由来
在JDK 5之前Java语言是靠synchronized关键字保证同步的,这会导致有锁
锁机制存在以下问题:
在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,引起性能问题。
一个线程持有锁会导致其它所有需要此锁的线程挂起。
如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级倒置,引起性能风险。
volatile是不错的机制,但是volatile不能保证原子性。因此对于同步最终还是要回到锁机制上来。
独占锁是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁用到的机制就是CAS,Compare and Swap。
什么是CAS
在Doug Lea提供的cucurenct包中,CAS理论是它实现整个java包的基石。
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”
通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新 值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法 可以对该操作重新计算。
CAS的优点
利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升
CAS存在的三大问题
ABA问题
循环时间长开销大
只能保证一个共享变量的原子操作
ABA问题
因为CAS需要在操作值的时候检查下值有没有发生变化,如果没有发生变化则更新,但是如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。ABA问题的解决思路就是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
————————————————
版权声明:本文为CSDN博主「majic_Jing」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/majic_Jing/article/details/107807856
JDK中相关原子操作类的使用
JDK中相关原子操作类比较多,有下面这些
更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference
原子更新字段类: AtomicReferenceFieldUpdater,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater
下面简单的讲解几个类的基本用法
AtomicInteger
例子
import java.util.concurrent.atomic.AtomicIntegerArray;
public class UseAtomicInteger {
static AtomicInteger ai = new AtomicInteger(10);
public static void main(String[] args) {
System.out.println(ai.getAndIncrement());//先get 再Increment
System.out.println(ai.incrementAndGet());//先Increment再get
System.out.println(ai.addAndGet(5));//先add 5 再get
System.out.println(ai.get());//get
}
}
结果
10
12
17
17
AtomicReference
AtomicReference类提供了一个可以原子读写的对象引用变量。
例子
import java.util.concurrent.atomic.AtomicReference;
public class UseAtomicReference {
static AtomicReference<UserInfo> ar = new AtomicReference<UserInfo>();
public static void main(String[] args) {
UserInfo userInfo = new UserInfo("张三",10);
ar.set(userInfo);
UserInfo updateUserInfo = new UserInfo("李四",20);
ar.compareAndSet(userInfo, updateUserInfo);
System.out.println(ar.get().getName());
System.out.println(ar.get().getAge());
System.out.println(userInfo.getName());
System.out.println(userInfo.getAge());
}
static class UserInfo{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public UserInfo(String name,int age) {
this.name = name;
this.age = age;
}
}
}
结果
李四
20
张三
10
从结果我们可以看出,AtomicReference改变的并不是它原本的对象,而是它引用的对象。
AtomicStampedReference
AtomicStampedReference和AtomicMarkableReference 比较像
AtomicStampedReference 它关注的是对象被改变了几次
AtomicMarkableReference 关注的是对象有没有被改变
例子
import java.util.concurrent.atomic.AtomicStampedReference;
public class UseAtomicStampedReference {
static AtomicStampedReference<String> asr = new AtomicStampedReference<String>("aaaaa", 0);//第一个参数是对象,这里是String,第二个参数是初始版本戳,为int类型
public static void main(String[] args) throws InterruptedException {
final int oldStamp = asr.getStamp();//拿初始版本号
final String oldReference = asr.getReference();
System.out.println("oldReference:"+oldReference+" oldStamp:"+oldStamp);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +"当前变量值:"+oldReference+" 当前版本戳:"+oldStamp+"_"+asr.compareAndSet(oldReference, oldReference+"java", oldStamp, oldStamp+1));
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
int stamp = asr.getStamp();//拿初始版本号
String reference = asr.getReference();
System.out.println(Thread.currentThread().getName() +"当前变量值:"+reference+" 当前版本戳:"+stamp+"_"+asr.compareAndSet(reference, reference+"C", stamp, stamp+1));
}
});
thread1.start();
thread1.join();
thread2.start();
thread2.join();
System.out.println(asr.getReference()+"========"+asr.getStamp());
}
}
结果
oldReference:aaaaa oldStamp:0
Thread-0当前变量值:aaaaa 当前版本戳:0_true //返回true 成功,false 失败
Thread-1当前变量值:aaaaajava 当前版本戳:1_true
aaaaajavaC========2
AtomicIntegerArray
例子
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicArray {
static int[] value = new int[] {1,2};
static AtomicIntegerArray ai = new AtomicIntegerArray(value);
public static void main(String[] args) {
ai.getAndSet(0, 3);
System.out.println(ai.get(0));
System.out.println(value[0]);
}
}
结果
3
1