cas(Conmpare And Swap)比较和交换算法 例子:对int i = 0;进行i++操作。 1.读取数据i为0 2.对0自增后得到数值1 3.比较i的值是否还是原来的0 4.是的话把i赋值为1 类似于数据库乐观锁update myTable set i = i + 1 where i = 0;所以3和4步必须要同时执行,要有原子性。 比如AtomicInteger就使用了cas算法,它执行update myTable set i = i + 1 where i = 0;时,底层用了汇编命令lock cmpxchg将0改成1, cmpxchg(compare and exchange)是指使用cas算法把0改成1。 lock是保证cmpxchg命令操作内存时,只能同时有一个cpu进行操作,单cpu保证了顺序执行,实现了原子性。 cas可能会发生aba情况,aba情况是指线程a把0改成1过程中,可能有线程b把0改成1又改回了0。 某些业务场景下,出现aba情况可能会导致一些问题。 比如有一个售票系统规定当卖出第100张票时,会在页面上弹出"票已售完"的提示框,用AtomicInteger做累加计数, 用户1购买时读取当前票数为99张,此时用户2去购买又读取到为99张,然后用户1购买成功,此时页面弹框"票已售完", 突然某种原因导致购买失败(如银行交易返回错误等等)发生退票,用户2此时进行购买,对比发现还是99张票于是购买成功,页面又弹一个框"票已售完"。 如果多个用户同时发生以上情况,页面上可能会弹出好几个框,如果把弹框换成其他操作呢,多次操作也许会导致其他严重的问题发生。 解决aba的办法是可以加一个版本号,先读取版本号,进行更新时判断版本号没变才执行: int currentVersion = getCurrentVersion(); update myTable set i = i + 1 where i = 0 and version = currentVersion; 以下例子证明AtomicInteger存在aba问题,AtomicStampedReference使用了版本号解决了aba问题:
public class Aba {
private static AtomicInteger atomicInteger = new AtomicInteger(1);
private static AtomicStampedReference<Integer> atomicStampedReference =
new AtomicStampedReference<Integer>(1, 0);
public static void main(String[] args) throws Exception {
//获取当前数值,此时num = 1
int num = atomicInteger.intValue();
new Thread(new Runnable() {
public void run() {
//将1改成2
atomicInteger.compareAndSet(1, 2);
//将2改回1
atomicInteger.compareAndSet(2, 1);
}
}).start();
Thread.sleep(100);
//将1改成2
boolean isSuc = atomicInteger.compareAndSet(num, num + 1);
//更改成功,输出true
System.out.println(isSuc);
//获取当前数值,此时num = 1
num = atomicStampedReference.getReference().intValue();
//获取当前版本号
int stamp = atomicStampedReference.getStamp();
//另有一个线程把1改成2后,再把2改回1
new Thread(new Runnable() {
public void run() {
//将1改成2,同时把版本号加1
atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
//将2改成1,同时把版本号加1
atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
}
}).start();
Thread.sleep(100);
//将1改成2,同时把版本号加1,但此时真正的版本号已经由stamp变成了(stamp+2)了,所以会返回失败
isSuc = atomicStampedReference.compareAndSet(num, num + 1, stamp, stamp + 1);
//更改失败,输出false
System.out.println(isSuc);
}
}