CAS:Compare and Swap-----比较并交换。
java.util.concurrent包中借助CAS实现了区别于synchronized同步锁的一种乐观锁。
其原理是CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
传送门:【JUC】 Java中的CAS
2.1 ABA问题的产生
CAS算法实现的一个很重要的前提是需要取出内存中某个时刻的数据并在当下时刻比较并替换,那么在这个时间差内数据变化会产生ABA问题。
比如,线程T1从内存位置V取出A,同时,线程T2也从内存位置V取出A,并且线程T2进行了一些操作,快速的把A换成了B,又快速的把B换成了A,之后线程T1进行CAS操作,发现内存位置V的值还是A,操作成功。
尽管线程T1的CAS操作成功,但并不代表这个过程就是没问题的。
2.2 ABA问题的解决
在此之前先介绍一下原子引用:JDK的atomic包里AtomicReference。
AtomicReference是作用是对”对象”进行原子操作。
提供了一种读和写都是原子性的对象引用变量。原子意味着多个线程试图改变同一个AtomicReference(例如比较和交换操作)将不会使得AtomicReference处于不一致的状态。
AtomicReference和AtomicInteger非常类似,不同之处就在于AtomicInteger是对整数的封装,底层采用的是compareAndSwapInt实现CAS,比较的是数值是否相等,而AtomicReference则对应普通的对象引用,底层使用的是compareAndSwapObject实现CAS,比较的是两个对象的地址是否相等。也就是它可以保证你在修改对象引用时的线程安全性。
Demo:
package com.test.cas.aba;
import lombok.Getter;
import lombok.Setter;
import java.util.concurrent.atomic.AtomicReference;
/**
* 原子引用
*
* @author wangjie
* @version V1.0
* @date 2019/12/16
*/
public class AtomicReferenceDemo {
public static void main(String[] args) {
User zs = new User("zs", 22);
User ls = new User("ls", 22);
AtomicReference userAtomicReference = new AtomicReference<>();
userAtomicReference.set(zs);
System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\t"+userAtomicReference.get().toString());
System.out.println(userAtomicReference.compareAndSet(zs, ls)+"\t"+userAtomicReference.get().toString());
}
@Getter
@Setter
class User{
private String name;
private int age;
public User(String name,int age){
this.age = age ;
this.name = name;
}
}
}
运行结果:
然后再用JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。
简单说就是加个类似时间戳的标志。
如果当前引用 == 预期引用,并且当前标志等于预期标志,则以原子方式将该引用和该标志的值设置为给定的更新值。
以下是Demo:
package com.test.cas.aba;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* todo
*
* @author wangjie
* @version V1.0
* @date 2019/12/16
*/
public class ABADemo {
private static AtomicReference atomicReference=new AtomicReference<>(100);
private static AtomicStampedReference stampedReference=new AtomicStampedReference<>(100,1);
public static void main(String[] args) {
System.out.println("===以下是ABA问题的产生===");
new Thread(()->{
atomicReference.compareAndSet(100,101);
atomicReference.compareAndSet(101,100);
},"t1").start();
new Thread(()->{
//先暂停1秒 保证完成ABA
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get());
},"t2").start();
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("===以下是ABA问题的解决===");
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
//暂停1秒钟t3线程
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 第2次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()+"\t 第3次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
},"t3").start();
new Thread(()->{
int stamp = stampedReference.getStamp();
System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
//保证线程3完成1次ABA
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
boolean result = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName()+"\t 修改成功否"+result+"\t最新版本号"+stampedReference.getStamp());
System.out.println("最新的值\t"+stampedReference.getReference());
},"t4").start();
}
}
注:文章内所有测试用例源码:https://gitee.com/wjie2018/test-case.git