CAS机制是什么
CAS是一种乐观锁机制,先看一个例子,开启两个线程,循环10000次,每次让count值加1,如果线程是安全的那么得到的结果应该是20000。
在没有使用锁的情况下
public class CASExample extends Thread {
public static int count = 0;
@Override
public void run() {
//循环的次数得设置大一些
for (int i=0; i<10000; i++) {
count++;
}
}
public static void main(String[] args) throws InterruptedException {
CASExample task1 = new CASExample();
CASExample task2 = new CASExample();
task1.start();
task2.start();
Thread.sleep(2*1000);
System.out.println(count);
}
}
输出为12161
反复运行几次还是没有预期的值20000,这是因为很可能存在两个线程同时访问这个变量count值。
如果使用悲观锁 synchronized ,我们可以得到我们预期要的结果,但是悲观锁的效率没有乐观锁的效率高。
使用synchronized 悲观锁
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample2 extends Thread {
public static int count = 0;
public static long getTimeStamp(){
Date date = new Date();
long timestamp = date.getTime();//返回13位时间戳
return timestamp;
}
//public static AtomicInteger count = new AtomicInteger();
@Override
public void run() {
//循环的次数得设置大一些
System.out.println();
long time1 = getTimeStamp();
for (int i=0; i<10000; i++) {
synchronized(CASExample2.class){
count++;
}
//count.incrementAndGet();
}
long time2 = getTimeStamp();
System.out.println("线程 " + Thread.currentThread().getName() + ",时间差值 "+(time2-time1));
}
public static void main(String[] args) throws InterruptedException {
CASExample2 task1 = new CASExample2();
CASExample2 task2 = new CASExample2();
task1.start();
task2.start();
Thread.sleep(2*1000);
System.out.println(count);
}
}
输出
import java.util.Date;
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample2 extends Thread {
// public static int count = 0;
public static long getTimeStamp(){
Date date = new Date();
long timestamp = date.getTime();//返回13位时间戳
return timestamp;
}
public static AtomicInteger count = new AtomicInteger();
@Override
public void run() {
//循环的次数得设置大一些
System.out.println();
long time1 = getTimeStamp();
for (int i=0; i<10000; i++) {
// synchronized(CASExample2.class){
// count++;
// }
count.incrementAndGet();
}
long time2 = getTimeStamp();
System.out.println("线程 " + Thread.currentThread().getName() + ",时间差值 "+(time2-time1));
}
public static void main(String[] args) throws InterruptedException {
CASExample2 task1 = new CASExample2();
CASExample2 task2 = new CASExample2();
task1.start();
task2.start();
Thread.sleep(2*1000);
System.out.println(count);
}
}
输出显然乐观锁AtomicInteger的执行效率是优于悲观锁的synchronized。其中AtomicInteger用到了CAS机制,CAS全称是 Compare And Swap即比较并替换。CAS有3个操作数
- 内存值V
- 旧的预期值A
- 要更改的新值B
CAS说的直白点就是在更新数据前,先判断内存中的值是否和预期的值是否相同,相同则更新,不同则等待,这个等待并重新尝试的过程则是自旋,直至修改完成。比如在上面的例子中
- 线程1想要执行count=count+1,此时线程1获取到的内存值V的旧的预期值A的值为100,想要更改的新值B为101,
- 在线程1提交前,线程2提前完成了地址V中所对应值的修改,此时地址V所对应的旧值A变为101了,
- 线程1使用CAS机制比较并替换原则,发现地址V中的旧的预期值A不再是100,提交失败。
- 线程1 重新获取地址V所对应的A值为101了,同时,B值也变为102,这个重复等待并尝试的过程则是自选。
- 此时线程1不再有其它的线程抢在它前面提交,终于提交成功,将count值修改为102。
显然,在高并发的时候,这个自旋的过程会导致cpu的开销较大。
ABA问题
ABA的问题需要通过例子说明,我们还是用上面的例子
- 线程1想要执行操作,此时线程1获取到的A值是100,然后由于某种原因,线程1被挂起了。
- 线程2也执行操作,先将A的值改为B=101,随后又将B的值改为100,此时V所对应的数据又是100。
- 线程1随后被唤醒,并通过比较并替换原则,提交成功。
以上便是ABA问题,线程更新数据并不关心数据中间是否被修改过。
我们可以通过给增加版本号的方式解决这个问题。