面试题总结——乐观锁与悲观锁
一、乐观锁与悲观锁对比
1.悲观锁
共享资源每次只给一个线程使用,其他线程阻塞,需等到当前线程使用完后才能使用(总是假设最坏的情况,每次拿到数据后都会认为别人会修改该数据,因此每次拿数据时都会上锁,阻塞其他人)
MySQL中的锁机制:行锁,表锁,读锁,写锁;Java中的synchronized和ReentrantLock独占锁。
2.乐观锁
总是假设最好的情况,每次拿数据时都认为别人不会修改,因此不会上锁,但在更新时会判断一下在此期间别人有没有更新这个数据,使用版本号机制和CAS算法实现。
MySQL中的版本号机制;Java中juc.atomic中的原子变量类。
3.乐观锁(CAS/版本号)的缺点
①ABA问题:旧值A先变为了B,又被变为了A,CAS检查时会发现值没有变化->添加版本号;
②多个线程的自旋会浪费大量处理器资源:线程CAS过程中在CPU上跑无用指令->自适应自旋/使用Java1.6的全新锁机制;
③线程获取锁的公平性:处于自旋状态的线程优先于处于阻塞状态的线程获取到锁,这就有可能导致处于阻塞状态的线程一直获取不到锁->内建锁(synchronized)无法实现公平机制,lock体系可以实现公平锁。
4.使用场景
乐观锁:适用于读多写少的场景,冲突很少发生,省去了锁的开销,加大了系统的吞吐量;
悲观锁:适用于读少写多的场景,经常发生冲突,若采取乐观锁可能会导致上层应用不断地进行retry,降低性能。
二、乐观锁的实现方式
1.版本号机制
数据库中的数据表添加一个数据版本号version
字段,表示数据被修改的次数,数据一旦被某事务修改,version就会+1,当事务A要更新数据值时,在读取数据的同时也会读取version值,若对数据进行更新,则version+1,在提交更新时,只有提交版本大于记录当前版本时才能执行更新操作
。否则重试更新操作,直到更新成功。
2.CAS算法
compare and swap:V(实际的内存值),A(当前线程认为的值),B(拟写入的值),V==A,CAS通过原子方式用新值B来更新V的值,否则不断地重试。