乐观锁与悲观锁的原理

1.1乐观锁与悲观锁

悲观锁:

总是假设最坏的情况,当一个线程每次去拿数据的时候都认为其他线程会修改数据,所以线程每次就会在数据处理之前上锁,这样其他线程想拿到这个数据就会阻塞,直到获取到锁为止。悲观锁的实现往往依靠数据库提供的锁机制,即在数据库中,对数据操作前给记录加排他锁,如果锁获取失败,则说明数据正在被其他线程修改,当前线程则等待或者抛出异常,如果获取锁成功,则对记录进行操作,然后提交事务后释放排他锁。

下面看一个典型的例子:

    public int updateEntry(long id){
        //1.使用悲观锁获取指定记录
        //for update是一种行级锁,也就是排他锁,在mysql中,如果sql语句明确主键,那么只锁这一条记录
        //如果不明确或者没有主键,那么它就会锁整张表,直到commit时释放
        EntryObject entry= query("select * from table where id=#{id} for update",id);
        
        //2.修改记录内容,根据计算修改entry记录的属性
        String name=generatorName(entry);
        entry.setName(name);
        ......
        
        //3.update操作
        int count=update("update table set name=#{name},age=#{age} where id=#{id}",entry);
        return count;
    }
   如上述代码,如果updateEntry(),query(),update()都使用了事务切面的方法,并且事务的传播行为是required(如果当前没有事务,新建一个事务,如果已存在一个事务,则加入这个事务)。执行updateEntry()方法时如果上次没有开启事务,它就会创建一个事务,进入方法里面,执行到1处时,由于传播行为时required,query方法不会新增事务,而是加入updateEntry()的事务,并且开启行级锁,直到事务结束,也就是updateEntry方法结束,事务才提交,锁才结束,下面2,3同理,当多个线程调用updateEntry(),切id相同,只会由一个线程成功,其他线程阻塞,保证了同一时间只有一个线程可以对记录进行修改。

乐观锁:

总是假设最好的i情况,每次去拿数据的时候都认为其他线程不会修改数据,所以不会上锁,只要在进行数据提交更新的时候,才会正式对数据冲突与否进行检测。

更改上述悲观锁代码:

   
 public int updateEntry(long id){
        //1.使用乐观锁获取指定记录
        //这里去掉了for update
        EntryObject entry= query("select * from table where id=#{id}",id);
        
        //2.修改记录内容,根据计算修改entry记录的属性
        String name=generatorName(entry);
        entry.setName(name);
        ......
        
        //3.update操作
        //这里加了一个version字段,每次去判断一下是否与数据库的一致
        int count=update("update table set name=#{name},age=#{age},version=${version}+1 where id=#{id} and version=#{version}",entry);
        return count;
    }
   如上述代码。当多个线程执行updateEntry方法时,并没有排他锁的存在,都会先经过1,2操作,将记录放入线程本地栈里面并进行修改。当进行当3的update时,假设此时version值为0,由于update语句本身时原子性的,假设线程A执行成功了,此时version变为了1,其他线程进行更新时,发现数据库中的version已经变为1了,而他们原先拿到的是0,不匹配了,此时就没有修改成功,返回的count影响行为0.上面version+1操作类似于CAS操作。

经过上述过程,接下来线程由两种选择,一种是更新失败,一种是重试

重试操作代码如下:

   
 public int updateEntry(long id){
        boolean result =false;
        int retryNum=5;
        
        while(retryNum>0){
        //1.使用乐观锁获取指定记录
        EntryObject entry= query("select * from table where id=#{id}",id);
        
        //2.修改记录内容,根据计算修改entry记录的属性
        String name=generatorName(entry);
        entry.setName(name);
        ......
        
        //3.update操作
        int count=update("update table set name=#{name},age=#{age},version=${version}+1 where id=#{id} and version=#{version}",entry);
        return count;
         
            if(count == 1){
                result=true;
                break;
            }
            retryNum--;       
       }
        return result;
  }
如上述代码所示:使用retryNum设置更新失败后重试次数,每失败一次重新去数据拿新的version值,类似于CAS的自旋操作,只是这里不是死循环,而是指定了尝试次数

乐观锁并不会使用数据提供的锁机制,一般在表中添加version字段或者使用业务状态来实现。乐观锁直到提交时才锁定,所以不会产生任何死锁

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值