美文网首页
悲观锁和乐观锁总结

悲观锁和乐观锁总结

作者: 2like99 | 来源:发表于2017-04-24 23:24 被阅读0次

    概念

    乐观锁也叫共享锁、读锁,事务T给对象A加了乐观锁,则其他事务还可以给对象A加乐观锁。但不能加被排他锁。也就是可以同时读数据,但读的过程中,不允许任何写。

    悲观锁也叫排他锁、写锁,事务T给对象A加了悲观锁,则其他事务不可以给对象A加任何锁。也就是在写期间,不允许任何读、写操作。

    实现

    在数据库用select for update属于乐观锁,update/delete属于悲观锁。

    在java中,同步块属于悲观锁。ReentrantReadWriteLock有ReadLock/WriteLock,分别实现乐观锁和悲观锁。注意:java的锁只对单个jvm有效。所以在集群环境下,必须借助数据库或者第三方组件(例如:zookeeper,redis)。

    应用场景

    减库存:库存为10,每一个订单库存减1。
    分析:减库存分两个步骤:1.判断是否有库存(库存为0);2.如果有则减1,如果没有则返回。
    并发环境下,存在两个问题:

    1. A/B两个用户同时判断库存都只剩一件,进行减1操作,产生超发。
    2. 同时进行对库存做减1操作,库存少减了1(例如库存10。两个进程同时来减1,A进程做stock=10-1,B进程也做stock=10-1,则库存变成了9,而不是期望的8)。
      假设库存是一个变量stock 。

    ** 方案1(悲观锁)**:判断stock 是否大于0前,先将变量加一把写锁,然后再判断。这样其他进程连判断语句都进不了。

    private final ReadWriteLock lock = new ReentrantReadWriteLock();   
    private final Lock w = lock.writeLock();  
        
    w.lock();
    if(stock  >0) stock --; 
    else return false;
    

    方案2(乐观锁):判断A是否大于0前,先将变量加一把读锁,判断成功准备写之前,将读锁升级为写锁。

    private final ReadWriteLock lock = new ReentrantReadWriteLock();  
    private final Lock r = lock.readLock();  
    
    r.lock();
    if(a>0) {
      w.lock(); 
      a--; 
    }
    else return false;
    

    在集群环境下的方案:借助数据库的悲观锁和乐观锁。由于数据库的锁资源非常宝贵,为了增加系统的吞吐量,一般采用版本号的方案。

    假设数据库表Stock,有两个字段num,version,初始<10,0>。

    select * from Stock;  //这里并没有加锁
    if(Stock.num>0) {
      ver = Stock.version;
      update Stock set num=num-1,version=version+1 where version = ver;
      if (影响的行数 == 1) {
         return true;
      } else {
        return false;
      }
    } else {
       return false;
    }
    

    另外,可以将上述方案结合起来。在java端做一个库存是否为空的变量(静态),这样就避免在库存已经为0的情况下还去查数据库。

    private final ReadWriteLock lock = new ReentrantReadWriteLock();   
    private final Lock w = lock.writeLock();  
    static boolean isZero = false;
    
    r.lock();
    if(!isZero) {
    select * from Stock;  //这里并没有加锁
    if(Stock.num>0) {
      ver = Stock.version;
      update Stock set num=num-1,version=version+1 where version = ver;
      if (影响的行数 == 1) {
        //判断是否还有库存并修改变量
        select count(1) cnt from Stock  where num = 0;
        w.lock();
        isZero  = true;
        w.unlock();
         return true;
      } else {
        return false;
      }
    } else {
       return false;
    }
    

    这里简化了一个问题,库存是针对某件商品的,所以应该是一个Map的锁。如果直接针对Map加锁势必导致多个商品同时被加锁。可以使用:

    1. java.util.concurrent.ConcurrentHashMap 类;
    2. 利用ReentrantReadWriteLock实现一个ReadWriteMap。见网络。

    相关文章

      网友评论

          本文标题:悲观锁和乐观锁总结

          本文链接:https://www.haomeiwen.com/subject/qgdmzttx.html