悲观锁与乐观锁
悲观锁
一种悲观的思想,总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就使用到了很多这种锁机制,比方行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比方Java里面的同步原语synchronized关键字的实现也是悲观锁。
乐观锁
一种乐观的的思想,每次取数据都假设别人不会修改,所以不会上锁。当需要更新数据时,会检测这个数据是否已经被其他人更新了,如果已经被别人更新过了,就返回让用户自己选择怎么处理。乐观锁适用于读多写少的场景。
java中解决临界区代码安全问题有两种办法:
阻塞式方法:使用synchronized或者Lock进行加锁。这其实就是一种悲观锁的思想。
非阻塞式方法:使用原子变量的方法。这就是一种乐观锁的思想。
java中乐观锁是通过CAS(compare and swap)方式实现的。
CAS算法
CAS算法有三个操作数,内存地址V,旧值A,新值B。
CAS算法的思想:当需要更新值时,先将旧值A与内存地址V中的值进行比较,如果相同,就说明没有其他人对这个值作更新,此时就可以执行更新操作;如果发现旧值A与内存地址V中的值不同,说明有其他人更新过这个值,此时将旧值A换成别人修改过的值,继续进行检测,直到检测到内存地址V中的值和A的值一样时,就执行更新操作。下面代码来模拟CAS算法。
public class CAS { private int value; //代表内存地址中的值 public synchronized int compareAndSwap(int A, int B){ //java中实际上没有使用synchronized关键字,而是通过操作硬件实现的 int oldValue = value; if(oldValue == A){ //如果A的值和内存地址中的值相同就进行更新 value = B; } return oldValue; //返回上次内存地址中的值,在更新失败时将此值赋给A } }
CAS 有效地说明了“ 我认为位置 V 应该包含值 A;假如包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值就可。 ”这其实和乐观锁的冲突检查+数据升级的原理是一样的。
CAS的优点:
1、没有使用锁,不存在死锁的情况。
2、在读多写少的情况下不需要每次都加锁,不会使其他线程阻塞。
CAS的缺点:
1、如果线程读写频繁,则循环检测会浪费CPU很多资源。
2、只能保证一个共享变量的原子操作。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。
3、存在ABA问题。
ABA问题:
在上面的算法中存在这样一个问题,如果线程1将A修改为B,线程2又将B修改为A,这时线程C想要修改这个值,他看到的还是A,操作可以顺利进行,但是却不知道A->B->A的这个变化。解决ABA问题可以在CAS方法中加入版本号,每次修改都使版本号加1,这样只要做了修改,版本号就会有变化。