乐观锁、独占锁
独占锁:是一种悲观锁;独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
乐观锁:每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。
乐观锁用到的机制就是CAS,Compare and Swap。
cas介绍
CAS的全称是compare and swap
CAS的原理其实很简单,为了保证:在多线程环境下,一个线程在更新某个对象的时候,没有其他的线程对该对象进行修改。
在线程更新某个对象(或值)之前,先保存更新前的值,然后在实际更新的时候传入之前保存的值,进行比较,如果一致的话就进行更新,否则失败。
cas为什么是无锁的
在大多数处理器的指令中,都会实现 CAS 相关的指令,这一条指令就可以完成“比较并交换”的操作,也正是由于这是一条(而不是多条)CPU 指令,所以 CAS 相关的指令是具备原子性的,这个组合操作在执行期间不会被打断,这样就能保证并发安全。由于这个原子性是由== CPU 保证==的,所以无需我们程序员来操心。
什么是cas的ABA问题
定义
并发编程种的 ABA 问题:
因为 CAS 需要在操作值的时候,检查某地址的内容有没有发生变化(和旧值进行比较),如果没有发生变化则更新为新的值。但是如果一个值原来是A,变成了 B,又变成了 A,那么使用 CAS 进行检查时会发现它的值没有发生变化,但是实际上却变化了。即:这个线程在操作的时候,其他线程也在进行操作。
原因
CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。
范例
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。如果链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。
如何解决ABA问题
解决方案:
ABA 问题的解决思路就是使用版本号。
思路很简单:每次compareAndSwap后给数据的版本号加1,下次compareAndSwap的时候不仅比较数据,也比较版本号;值相同,版本号不同也不能执行成功。那么 A→B→A 就会变成 1A→2B→3A。
范例
甲乙线程想改变三角形 A 的形状,乙线程先改成了四边形,后又改成了三角形,三角形 A1->四边形 V2-> 三角形 A3。
当甲线程想改变 A3 为五边形时报错,因为三角形经过已线程修改后,前后版本号不一样,被判定为已修改过,其他线程不能修改。这样就防止了 ABA 问题。
java中的实现
- 如何每次都让版本号更新的?
AtomicStampedReference 内部维护了一个 Pair的数据结构,用volatile修饰,保证可见性,用于打包数据对象和版本号.
private static class Pair<T> {
final T reference;
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
}
它的compareAndSet方法如下:
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
-
分析
1》首先判断传入的参数是否符合 Pair 的预期,从数据和版本号两个方面来判断,有一个不符合就打回;
2》如果符合预期,且传入的新的参数与Pair中的一样:直接返回true,不用更新;
3》如果符合预期,新的参数和Pair中不一样:则 使用casPair来比较交换当前的Pair与传入参数构成的Pair;casPair又调用compareAndSwapObject来交互Pair属性。 -
总结
AtomicStampedReference是通过加版本号来解决CAS的ABA问题。至于怎么加版本号,因为compareAndSwapObject只能对比交互一个对象,所以只需要将数据和版本号打包到一个对象里就解决问题了。