悲观锁
悲观锁总是假设最坏的情况,每次去拿数据的时候认为别人都会修改数据,所以每次拿数据的时候都会上锁。关系型数据库经常会用到,比如表锁和行锁。
代表就是synchronized和Reentrantlock,经常用于多写场景。
乐观锁
乐观锁总是假设最好的情况,每次去拿数据的时候都认为别人不会更改数据,所以不会上锁。但是在更新的时候会判断一下在此期间别人有没有更改数据。
乐观锁常用于多读场景冲突发生少,省去了锁的开销,提高了整个系统的吞吐量。若是多写场景会发生较多冲突,系统就会不停retry,会降低系统的效率。
乐观锁常见的两种方法:
-
版本号机制:
一般在数据表中添加一个版本号version字段,表示数据被修改的次数。当数据被修改时,version值加1。当线程A要更新数据时会读取version值,提交更新时会再次读取version值,若两次读取的version值相同才提交更新,否则会重新读取version执行更新操作。 -
CAS(compare and swap) 比较与交换
无锁算法。没有线程被阻塞的情况下实现同步,也叫非阻塞同步。
CAS涉及3个操作数:
1、需要读写的内存值V
2、进行比较的值A
3、拟写入的新值B
当且仅当V值等于A时,CAS通过原子方式用新值B更新V值。
乐观锁的缺点:
-
ABA问题。
如果一个变量V初次读取的时候是A值,再次读取准备赋值的时候仍然是A值。但不能保证没有被其他线程修改过值,因为可能修改又改回A,CAS会误认为它从未被修改过。
解决方法:
AtomicStamReference类的compareAndSet方法。
当前引用是否是预期引用,当前标志是否是预期标志,若全部相等更新值。 -
循环开销大
CAS或版本号一直没有成功,会自旋重试,cpu执行开销非常大。
解决方法:
pause指令有一定的提升。
1、延迟流水线执行命令(de-pipeline)。
2、可避免在退出循环的时候因内存顺序冲突引起cpu流水线被清空,提高cpu执行效率。 -
只能保证一个共享变量的原子操作。
解决办法:
AtomicReference类可以将多个变量放在一个对象里进行CAS操作。