当我们想要在业务中引入“乐观锁”时,应该要思考清楚它的概念,为了更加形象的理解“乐观锁”,我们可以先看一下认识下悲观锁。
什么是悲观锁
顾名思义,悲观锁是基于一种悲观的态度来防止一切数据冲突,它是以一种预防的姿态,并在修改数据之前把数据锁住,然后再对数据进行读写。在它释放锁之前任何线程都不能对其数据进行操作,直到持有锁的线程释放锁之后,其它线程才能通过竞争去获取到悲观锁,自动对数据进行加锁,然后才可以对数据进行操作;
很多技术类的文章中,也将“悲观锁”叫做“互斥锁”或者“排它锁”。
悲观锁的特点:可以完全保证数据的独占性和正确性,因为每次请求都会先对数据进行加锁, 然后进行数据操作,最后再解锁,而加锁释放锁的过程会造成消耗,所以性能不高;
在Java中最常见的悲观锁就是synchronized和ReentrantLock,前者是隐式加锁,后者是显式加锁。
代码语言:javascript复制Object lock=new Object();
public void test1(){
synchronized (lock){
//添加需要加锁的逻辑
}
ReentrantLock reentrantLock=new ReentrantLock();
public void test2(){
reentrantLock.lock();
try {
//添加需要加锁的逻辑
}catch (Exception e){
}finally{
reentrantLock.unlock();
}
}
在数据库中,比如MySQL,我们可以通过添加“for update”加一个行锁,当然也可以叫“排它锁”。
代码语言:javascript复制###添加一个悲观锁
SELECT * FROM user WHERE id=3 FOR UPDATE;
什么是乐观锁
顾名思义,乐观锁是对于数据冲突保持一种乐观态度,操作数据时不会对操作的数据进行加锁(这使得多个线程可以并行的对数据进行操作),只有到数据提交的时候才通过一种机制来验证数据是否存在冲突(一般实现方式是通过加版本号然后进行版本号的对比方式实现);
特点:乐观锁是一种并发类型的锁,其本身不对数据进行加锁而是通过业务实现锁的功能。那么不对数据进行加锁就意味着允许多个请求同时访问数据,同时也省掉了对数据加锁和解锁的过程,这种方式因为节省了悲观锁加锁的操作,所以可以一定程度的的提升性能。不过在并发非常高的情况下,会导致大量的请求冲突,冲突导致大部分操作无功而返而浪费资源,所以在高并发的场景下,乐观锁的性能反而不如悲观锁。
在Java中,我们也可以将读写锁ReentrantReadWriteLock当作乐观锁的一种变种,当然最经典的乐观锁就是Java中的CAS,Java的JUC中用到乐观锁的地方,几乎都是用CAS实现的。
在MySQL中,最常见的乐观锁就是“版本号”,并在需要加乐观锁的表中添加一个字段用来记录版本号,比如最常见的就是“version字段”。
Spring Cloud Alibaba组合Mybatis-Plus真香
一般情况下,业务服务中使用的乐观锁最多的业务场景就是数据库,也就是添加“version”字段。如果是所有的表都要添加乐观锁,那么软件开发人员需要在所有的业务服务中,添加一遍乐观锁的逻辑,比如版本号的校验等等,太麻烦了。
使用Spring Cloud Alibaba组合Mybatis-Plus,添加乐观锁就只需要几行代码就行。
第一步,在数据库的业务表中添加一个“version”字段;
第二步,以Spring Cloud Alibaba作为基础框架,初始化一个微服务;
第三步,在微服务中添加如下pom依赖。
代码语言:javascript复制<!—引入Mybatis Plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.1</version>
</dependency>
<!—引入Mybatis-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!—引入适配Spring Framework的mybatis spring-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.6</version>
</dependency>
第四步,添加乐观锁的拦截器,比如我在项目中使用如下代码。
代码语言:javascript复制@Configuration
@MapperScan("com.alibaba.cloud.youxia.mapper")
public class MybatisPlusOptLockerConfig {
/**
* 1. 注入一个Mybatis Plus的拦截器MybatisPlusInterceptor,并绑定一个子拦截器。
OptimisticLockerInnerInterceptor
* 2. OptimisticLockerInnerInterceptor是用来实现乐观锁的核心类
* @return
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//1.新建一个拦截器对象MybatisPlusInterceptor
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
//2.添加子拦截器OptimisticLockerInnerInterceptor
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
//3.返回给Spring Framework的IOC容器
return interceptor;
}
第五步,在实体类中添加version字段,比如我在项目中使用如下代码。
代码语言:javascript复制@Data
@TableName(value = "user")
public class Example2ProductEntity implements Serializable {
static final long serialVersionUID = -232434345545442L;
/**
* 1.乐观锁的版本号
* 2.如果注释掉这一行代码,则乐观锁失效
* 3.需要在数据库表中,添加一个version字段
*/
@Version
private Integer version;
}
完成上面五步,乐观锁就添加成功了,你就可以在业务服务中使用它了,关于乐观锁的原理,这里就不分析了。
当然如果还想提效,可以将乐观锁的拦截器,封装成一个Starter组件,这样业务都不用在每个服务中添加一遍了。
当然还可以提效,那就是下沉Dao层代码,这样业务服务几乎一行代码都不用改,就可以实现乐观锁,简单吧。
Spring Cloud Alibaba组合Mybatis-Plus真香
总结
咱们程序员在熟悉一个技术之前,一定要学会高效的去用,只有用了之后,才会去分析原理。
公众号初衷
知识输出是笔者的初衷,借助知识输出,能够认识更多的牛人,能够和牛人沟通,也是自己技术提升的一个机会。