悲观锁与乐观锁
悲观锁:悲观锁思想认为如果多个线程中使用共享资源,则它们肯定会同时进行修改从而引起冲突,悲观锁的解决方式是共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁:乐观锁思想认为如果多个线程使用共享资源,它们修改应该是有先后顺序的,不会同时进行修改,如果真的有冲突则后面修改失败。乐观锁的解决方式是共享资源可以由多个线程同时访问修改,对于冲突失败,让其重试直到成功即可。
什么是CAS
CAS全程是compareAndSwap,维基百科上的中文称为“比较并交换”,是乐观锁的一种实现方式,涉及有三个操作数,内存位置(V)、预期值(A)和新值(B),如语句CAS(V,A,B),当*V等于A时,则将值替换为新值B。虽然从语言描述上来说是分为多个操作的,但实际上CAS操作是一个原子操作,是基于CPU提供的原子操作指令实现的。如下CAS实现更新的伪代码:
do {
A=current();//*V赋予A,A为就是预期值
B=update();//获取B作为新值
} while(!compareAndSwap(V, A, B));
- 1
- 2
- 3
- 4
- 5
- 6
*V:使用了C语言描述,表示V内存位置的值,便于描述
更新:我们所说的更新是指更新内存位置V的值,也就是改变*V
上面伪代码将*V赋予A,然后获取新值赋予B,最后进行交换判断直到成功,例如:
- | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|
线程1 | A=5 | B=6 | CAS:false | |||
线程2 | A=5 | B=7 | *V=7 |
上面两个线程同时进入循环进行进行更新操作,第一轮循环中只有线程2更新成功,线程1因为*V的值被线程2改变导致和预期值不一致从而失败,只能重新进入下一轮循环直到成功。
CAS解决了什么问题
其实这个问题前面介绍悲观锁和乐观锁时其实已经回答,当多个线程操作时,它解决了悲观锁使用了独占锁,一次只能有一个线程进入临界区的问题,在竞争状态比较低的情况下提高了并发性能。为何说是竞争低的情况下,如果上面有很多个线程同时进入循环,那么每个线程都在占用资源执行,但每次只有一个线程能更新成功。
CAS缺点
1.当竞争很强烈时,每个线程都占用资源执行但是只有一个成功
2.ABA问题,上面的例子中,如果还有一个线程3在修改,并将线程2修改后的值又变成了5,那么线程1此时是察觉不到的,它还能进行成功的执行!例如:
- | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
---|---|---|---|---|---|---|---|---|---|
线程1 | A=5 | B=6 | *A=6 | ||||||
线程2 | A=5 | B=7 | *V=7 | ||||||
线程3 | A=7 | B=5 | *A=5 |
ABA问题解决方式:引入一个版本号,在每次更新后都加1,在上面的例子中,即时线程3将*V修改回来原值,因为版本号不一致也会导致失败重试。
CAS在java.util.concurrent有着广泛的使用,如AtomicInteger。且对于ABA问题,Java也提供了AtomicStampedReference来处理。