悲观锁


认为随时有可能发生冲突,用锁保护所有临界区。日常使用的锁绝大多数都是悲观锁。

优点:

1. 确保安全性,悲观锁临界区内不会发生并发问题。

2. 简单方便。

3. 使用悲观锁,在临界区内操作数据成功率高。

缺点:

1. 如果临界区内耗时长,会影响程序整体工作效率。

2. 可能产生死锁。

乐观锁


乐观的认为不会发生并发冲突,不为临界区代码加锁,但会持有在运行临界区前的版本号。在完成临界区后对比版本号,如果版本号没被更新说明没有产生冲突,进行数据更新并更新版本号。如果发现版本号被其他线程更新,则回滚所有操作并重试。乐观锁不是锁,是一种概念。

优点:

1. 不会产生死锁问题。

2. 在并行冲突情况低的多读程序中,工作效率比悲观锁高。

3. 允许多个线程进入临界区,在不同时对某些数据修改的情况下,效率很高。

缺点:

1. 对于回滚代价大的程序,不易使用乐观锁。

2. 冲突情况多时,效率不一定比悲观锁高。

3. 乐观锁实现复杂。

JAVA中乐观锁与悲观锁


java中较为常用的锁是synchronized和ReentrantLock,实际上这两种锁都是悲观锁,悲观锁就是一般的锁,java并不强调悲观和乐观的概念。在java并发里悲观和乐观的区分不如说是上锁和Ad-hoc线程封闭的区别。通俗的理解,悲观就是用锁控制多线程访问,乐观更多的是用程序本身的规则约束数据不能被错误的访问到。

如何在java中实现乐观锁?回到乐观锁的概念,用版本来控制数据不被错误的访问。在你的java程序中添加int活着double型的version字段,但是你要确保版本的访问和最后数据的修改锁是被锁保护的,这样做大概采用了乐观锁的概念,能够起到的效果是减少了临界区的范围,如果你的事务很长,采用这种方法尚可。不要在不能使用乐观锁的地方强行使用乐观锁,它的概念不是为java而生的而是数据库。不要弄巧成拙。

数据库中乐观锁与悲观锁


悲观锁实现

在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。

如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。

如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。

其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

悲观锁是一种默认产生冲突的处理概念,上述四个步骤表达了悲观锁的含义。



        1 
      

        2 
      

        3 
      

        4 
      
 
        start  
        transaction 
        ; 
       
 
        select 
        A  
        from 
        B  
        where 
        C  
        for 
        update 
        ; 
       
 
        update 
        B  
        set 
        D; 
       
 
        commit 
        ; 
       

第一步开始事务,第二步select末尾for update表示在此次事务提交完成之前select查询到的语句被锁定,在事务结束之前不会被改变。

乐观锁实现

在大项目中,数据库的每一张表至少有10多个字段。即使是很简单的一张表也会有很多字段,这是因为每一张表都有相同的一些记录性字段,比如:最后修改时间、最后修改人、版本号…我们可以利用其中的版本号来实现乐观锁。


        1 
      

        2 
      

        3 
      

        4 
      
 
        select 
        A,version  
        from 
        B  
        where 
        C 
       
 
        ...(其他操作) 
       
 
        update 
        B  
        set 
        D,version=version+1  
       
 
        where 
        C  
        and 
        version = E;(E是已知最近版本号) 

单单是数据库不便于操作,可以结合代码记录版本号,事务完成后进行验证。