一、CAS
CAS:Compare and Swap,比较并交换。
java.util.concurrent包中借助CAS实现了区别于synchronized同步锁的一种乐观锁。
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
二、CAS缺点:
1. ABA问题:
就是说一个线程把数据A变为了B,然后又重新变成了A。此时另外一个线程读取的时候,发现A没有变化,就误以为是原来的那个A。导致可能存在潜藏的问题。这就是有名的ABA问题。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。
ABA的代码例子:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class AtomicTest{
private static AtomicReference index =new AtomicReference("A");
public static void main(String[] args) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"----->"+index.get());
index.compareAndSet("A","B");
System.out.println(Thread.currentThread().getName()+"----->"+index.get());
index.compareAndSet("B","A");
System.out.println(Thread.currentThread().getName()+"----->"+index.get());
},"线程A").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean flag=index.compareAndSet("A","C");
System.out.println(Thread.currentThread().getName()+"----->预期值是A吗:"+flag+"!更新后的值是"+index.get());
},"线程B").start();
}
}
控制台打印:
线程A----->A
线程A----->B
线程A----->A
线程B----->预期值是A吗:true!更新后的值是C
在上面的代码中,我们使用A线程,对index A-B-A的变化,然后B线程读取index观察是否有变化,读到的值认为没变化,并设置了新值C。实际index值已经会变化过了,如何使用AtomicStampedReference解决这个问题?
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;
public class Atomic2Test {
private static AtomicStampedReference atomicStampedRef =new AtomicStampedReference("A",0);
public static void main(String[] args) {
int stamp=atomicStampedRef.getStamp();
System.out.println(Thread.currentThread().getName()+"版本号----->"+stamp);
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"版本号----->"+atomicStampedRef .getStamp());
atomicStampedRef .compareAndSet("A","B",atomicStampedRef.getStamp(),atomicStampedRef.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"compareAndSet一次,版本号----->"+atomicStampedRef .getStamp());
atomicStampedRef .compareAndSet("B","A",atomicStampedRef.getStamp(),atomicStampedRef.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"compareAndSet两次,版本号----->"+atomicStampedRef .getStamp());
},"线程1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
boolean flag=atomicStampedRef .compareAndSet("A","C",stamp,atomicStampedRef.getStamp());
System.out.println(Thread.currentThread().getName()+"----->看一下A是预期的版本"+stamp+"吗?"+flag+"那现在的版本是:"+atomicStampedRef .getStamp());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程2").start();
}
}
控制台打印:
main版本号----->0
线程1版本号----->0
线程1compareAndSet一次,版本号----->1
线程1compareAndSet两次,版本号----->2
线程2----->看一下A是预期的版本0吗?false那现在的版本是:2
我们会发现AtomicStampedReference里面增加了一个时间戳
,也就是说每一次修改只需要设置不同的版本好即可。
2、循环时间长开销大:
对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
3、只能保证一个共享变量的原子操作:
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。