乐观锁和悲观锁
悲观锁假设每次拿数据别人都会修改,所以是先取锁再访问,在数据库中的悲观锁是在对记录修改之前,先尝试为该记录加上排他锁,其他要对数据进行修改的事务只能等待。传统的关系型数据库用了很多悲观锁,比如行锁、表锁、写锁、读锁。synchronized和ReentrantLock独占锁都是悲观锁的思想。
乐观锁是假设每次拿数据别人不会修改,所以不会上锁,但是在提交更新之前会判断一下别人有没有修改这个数据,一般乐观锁的采用cas和版本号机制实现。
数据库的乐观锁一般是版本号机制,Java中java.util.concurrent.atomic下的原子变量就是用cas实现的。
乐观锁常见的2种的实现方式:cas和版本号
数据库中为数据增加一个版本标识,当读取数据的时候,把版本标识一起读出,数据每更新一次,同时对版本标识进行更新,在提交更新更新之前,再把数据的当前版本信息和第一次取出的对比,如果相等则更新,否则就是过期数据
cas是在不使用锁的情况下实现线程同步,就是没有阻塞的情况下实现线程同步。
乐观锁的问题:ABA,JDK 1.5 以后的 AtomicStampedReference 类可以解决这个问题;循环时间长开销大,自旋cas如果长时间不成功,会给CPU带来非常大的执行开销;而且cas只对一个变量有效,但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作。
CAS和synchronized的区别:对于资源竞争较少的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 “重量级锁” 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁 和 轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。
对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
两种锁的使用场景:乐观锁适合多读场景,写多的话还是悲观锁。
补充:(实现数据库版本的方式:版本号和时间戳。但是时间戳的话会有不同机器的时钟不同步,所以一般不用。
优缺点:悲观锁提供了安全但是效率方面会产生额外的开销。乐观锁相信事务之间争用数据概率小,不会产生任何锁和死锁,但是如果正巧两个事务都同时获取了数据库版本信息并同时写回也会问题。)
网友评论