CAS(Compare And Swap)
Atomic开头的类都被称为原子变量类
原子操作:不可再分的操作,这个操作要么全部完成,要么全不完成。
synchronized关键字包围的代码块就是一个原子操作
synchronized可以实现原子操作,但这个机制太重
CAS的原理
利用了现代处理器都支持的CAS的指令,
循环这个指令,直到成功为止
悲观锁和乐观锁:
Synchronized 是悲观锁 ,抢到锁后安安心心的做。
CAS 是乐观锁,先取出来再去改 ,用CAS指令去比较和交换(比较没人改过就交换,有人改了就再来一遍)
性能比较
大部分情况,原子变量要比加锁性能高
Synchronized 关键字加锁,多个线程竞争的时候,任意时刻只有一个线程能进入锁范围内,其他没有竞争成功的线程被阻塞,被阻塞会发生上下文切换,一次上下文切换牵涉的资源很多(一次上下文切换大约5000-20000个时钟周期,3-5ms)
CAS的一条指令0.6ns,算上自旋翻几十倍 600ns也比上下文切换快的多,而且上下文切换在唤醒时还会发生一次(一次拿锁至少发生两次上下文切换3-5ms*2)
整个JDK并发编程发展趋势-CAS机制,无锁化编程
高度竞争,特意设计的情况下Synchronized 性能好些,这种情况在目前生产中几乎是没有的。
CAS的问题
CAS这么强,为什么加锁的机制还有生存的余地?
1.ABA问题:
线程1: 拿到A,比较判断是不是A,是的话换成B
线程2跑的更快:拿到A,比较是A换成C又迅速换成A
当线程1比较的时候是A,以为没人改过,实际上被改过。
这就是ABA问题
ABA问题解决方法:
加一个版本戳
AtomicMarkableReference关心这个变量有没有人动过
AtomicStampedReference被动了几次
/**
*类说明:演示带版本戳的原子操作类
*/
public class UseAtomicStampedReference {
static AtomicStampedReference<String> asr
= new AtomicStampedReference("puppy",0);
public static void main(String[] args) throws InterruptedException {
//拿到当前的版本号(旧)
final int oldStamp = asr.getStamp();
final String oldReference = asr.getReference();
System.out.println(oldReference+" --------------- "+oldStamp);
Thread rightStampThread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+": 当前变量值:"
+oldReference + " 当前版本戳:" + oldStamp + " "
+ asr.compareAndSet(oldReference,
oldReference + "love", oldStamp,
oldStamp + 1));
}
});
Thread errorStampThread = new Thread(new Runnable() {
@Override
public void run() {
String reference = asr.getReference();
System.out.println(Thread.currentThread().getName()
+": 当前变量值:"
+reference + " 当前版本戳:" + asr.getStamp() + " "
+ asr.compareAndSet(reference,
reference + "donot", oldStamp,
oldStamp + 1));
}
});
rightStampThread.start();
rightStampThread.join();
errorStampThread.start();
errorStampThread.join();
System.out.println(asr.getReference()+" ------------- "+asr.getStamp());
}
}
puppy --------------- 0
Thread-0: 当前变量值:puppy 当前版本戳:0 true
Thread-1: 当前变量值:puppylove 当前版本戳:1 false
puppylove ------------- 1
2.开销问题:
较不相等则自旋 循环的次数长期不成功 那么开销大。
开销问题解决方法:
开销特别大,要么就改成加锁
3.只能保证一个共享变量的原子操作:
一个地址保证一个变量,现在cpu指令进行CAS修改时,只能针对某个地址进行修改,如果希望多个变量的修改是个原子操作,则CAS不能适应,就得使用加锁机制
只能保证一个共享变量的原子操作解决方法:
AtomicReference 解决只能保证一个共享变量的原子操作,把两个变量组合到一个对象中去
/**
*类说明:演示引用类型的原子操作类
*/
public class UseAtomicReference {
static AtomicReference<UserInfo> atomicUserRef;
public static void main(String[] args) {
UserInfo user = new UserInfo("puppy", 14);//要修改的实体的实例
atomicUserRef = new AtomicReference(user);
UserInfo updateUser = new UserInfo("puppylove",17);
atomicUserRef.compareAndSet(user,updateUser);
System.out.println(atomicUserRef.get());
System.out.println(user);
}
//定义一个实体类
static class UserInfo {
private volatile String name;
private int age;
public UserInfo(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "UserInfo{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
UserInfo{name='puppylove', age=17}
UserInfo{name='puppy', age=14}
CAS的使用,JDK提供了很多原子操作类:
Jdk中相关原子操作类的使用
更新基本类型类:AtomicBoolean,AtomicInteger,AtomicLong
更新数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
更新引用类型:AtomicReference,AtomicMarkableReference,AtomicStampedReference
/**
*类说明:演示基本类型的原子操作类
*/
public class UseAtomicInt {
static AtomicInteger ai = new AtomicInteger(10);
public static void main(String[] args) {
ai.getAndIncrement();//i++
ai.incrementAndGet();//++i
ai.addAndGet(24);//+24 返回加的新值
ai.getAndAdd(24);//+24 返回加的旧值
System.out.println(ai);
}
}
60