悲观锁、乐观锁

 

悲观锁(Pessimistic Lock)

顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,因此每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了不少这种锁机制,好比行锁,表锁等,读锁,写锁等,都是在作操做以前先上锁。java

咱们认为系统中的并发更新会很是频繁,而且事务失败了之后重来的开销很大,这样以来,咱们就须要采用真正意义上的锁来进行实现。悲观锁的基本思想就是每次一个事务读取某一条记录后,就会把这条记录锁住,这样其它的事务要想更新,必须等之前的事务提交或者回滚解除锁。算法

实现方式:

大多在数据库层面实现加锁操做,JDBC方式:在JDBC中使用悲观锁,须要使用select for update语句,e.g.sql

<code class="language-sql hljs ">Select * from Account 
where ...(where condition).. for update</code>

 

乐观锁(Optimistic Lock)

顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,因此不会上锁,可是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可使用版本号等机制。乐观锁适用于多读的应用类型,这样能够提升吞吐量,像数据库若是提供相似于write_condition机制的其实都是提供的乐观锁。数据库

咱们认为系统中的事务并发更新不会很频繁,即便冲突了也没事,大不了从新再来一次。它的基本思想就是每次提交一个事务更新时,咱们想看看要修改的东西从上次读取之后有没有被其它事务修改过,若是修改过,那么更新就会失败。数据结构

实现方式:

大可能是基于数据版本(Version)记录机制实现,何谓数据版本?即为数据增长一个版本标识,在基于数据库表的版本解决方案中,通常是经过为数据库表增长一个 “version” 字段来实现。并发

读取出数据时,将此版本号一同读出,以后更新时,对此版本号加一。此时,将提 交数据的版本数据与数据库表对应记录的当前版本信息进行比对,若是提交的数据 版本号大于数据库表当前版本号,则予以更新,不然认为是过时数据。
假如系统中有一个Account的实体类,咱们在Account中多加一个version字段,那么咱们JDBC Sql语句将以下写:
性能

<code class="language-sql hljs "><code class="language-sql hljs ">Select a.version....from Account as a 
where (where condition..)
 
Update Account set version = version+1.....(another field) 
where version =?...(another contidition)</code></code>

这样以来咱们就能够经过更新结果的行数来进行判断,若是更新结果的行数为0,那么说明实体从加载以来已经被其它事务更改了,因此就抛出自定义的乐观锁定异常。具体实例以下:spa

<code class="language-sql hljs "><code class="language-sql hljs "><code class="language-java hljs ">int rowsUpdated = statement.executeUpdate(sql);
if (rowsUpdated ==0 ) {
    throws new OptimisticLockingFailureException();
}</code></code></code>

 

 

典型的乐观锁:基于冲突检测的乐观锁(CAS自旋)

Synchronized互斥锁属于悲观锁,它有一个明显的缺点,它无论数据存不存在竞争都加锁,随着并发量增长,且若是锁的时间比较长,其性能开销将会变得很大。有没有办法解决这个问题?答案就是基于冲突检测的乐观锁。这种模式下,已经没有所谓的锁概念了,每条线程都直接先去执行操做,计算完成后检测是否与其余线程存在共享数据竞争,若是没有则让此操做成功,若是存在共享数据竞争则可能不断地从新执行操做和检测,直到成功为止,这种叫作CAS自旋.net

 

CAS(Compare And Swap)比较并转换 
算法涉及三个数:内存地址V,旧的预期值A,新的预期值B。当且仅当旧的预期值A和内存值V相同时,将内存值改成B,不然什么也不作。 上述的处理过程是一个原子操做(靠硬件来保证)。
如何来理解上面这一段话呢?咱们先了解一下乐观锁和悲观锁各自的作事方式,首先,悲观锁的态度是一件事情我必需要能百分之百掌控才能去作,不然就认为这件事情必定会出问题,而乐观锁的态度就是无论什么事情,我都会先尝试去作,大不了最后不成功就是了。 
基于CAS的自旋就是典型的乐观锁,程序执行时,线程1从共享内存中取值V并建一个副本A,对A进行计算后将新的值保存为B,而后对A值和内存中的V值进行比较,若是A等于V,则认为内存中的V值没有被其余线程修改过,能够将新值B赋给内存,不然,认为内存中已被其余的线程修改,则从新执行计算操做和检测,知道旧的指望值A等于内存值V为止。 
Java并发包java.util.concurrent.*的核心就是CAS自旋原理。如AtomicInteger、AtomicLong等都是基于CAS实现的。线程

 

两种锁的比较

两种锁各有优缺点,不可认为一种好于另外一种,像乐观锁适用于写比较少的状况下,即冲突真的不多发生的时候,这样能够省去了锁的开销,加大了系统的整个吞吐量。但若是常常产生冲突,上层应用会不断的进行retry,这样反却是下降了性能,因此这种状况下用悲观锁就比较合适。

 

乐观锁是假定读取的数据,在写以前不会被更新。适用于数据更新不频繁的场景。

相反,当数据更新频繁的时候,乐观锁的效率很低,由于基本上每次写的时候都要重复读写两次以上。

  1. 对于数据更新频繁的场合,悲观锁效率更高 ;

  2. 对于数据更新不频繁的场合,乐观锁效率更高;

通常来讲若是并发量很高的话,建议使用悲观锁,不然的话就使用乐观锁。

若是并发量很高时使用乐观锁的话,会致使不少的并发事务回滚、操做失败。

相关文章
相关标签/搜索