悲观锁和乐观锁总结

概念

乐观锁也叫共享锁、读锁,事务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。见网络。

你可能感兴趣的:(悲观锁和乐观锁总结)