跳到主要内容

乐观锁和悲观锁

乐观锁和悲观锁是在并发编程中用于处理多个线程访问共享资源的两种不同策略

什么是乐观锁?

乐观锁的核心思想是假设在大多数情况下,不会发生冲突,因此允许多个线程同时访问共享资源,但在更新资源时,每个线程需要首先检查资源的版本信息(通常是一个版本号或时间戳)。如果多个线程尝试更新同一资源,只有其中一个线程能够成功,而其他线程需要处理冲突,通常是重新读取资源并尝试更新。乐观锁通常适用于读操作频繁,写操作较少的情况,以减少锁的竞争和提高系统的并发性能。

什么是悲观锁?

悲观锁的核心思想是假设会发生冲突,因此在访问共享资源时,会使用锁来限制只允许一个线程访问资源,其他线程必须等待。悲观锁通常通过数据库中的行级锁或Java中的synchronized关键字来实现。悲观锁的优点是它可以确保资源的一致性,但缺点是可能会导致性能问题,因为它限制了并发性。

Mybatis-Plus实现乐观锁

1.数据库添加商品表

CREATE TABLE product (
id BIGINT ( 20 ) NOT NULL COMMENT '主键ID',
name VARCHAR ( 30 ) NULL DEFAULT NULL COMMENT '商品名称',
price INT ( 11 ) DEFAULT 0 COMMENT '价格',
version INT ( 11 ) DEFAULT 0 COMMENT '乐观锁版本号',
PRIMARY KEY ( id )
);

2.添加数据

INSERT INTO product (id, NAME, price) VALUES (1, '小米14Pro', 100);

3.添加实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
private Long id;
private String name;
private Integer price;
private Integer version;
}

4.添加ProductMapper

@Mapper
public interface ProductMapper extends BaseMapper<Product> {
}

5.模拟测试

@Autowired
private ProductMapper productMapper;

@Test
void test() {
//管理员1
Product p1 = productMapper.selectById(1L);
System.out.println("管理员1取出的价格:" + p1.getPrice());
//管理员2
Product p2 = productMapper.selectById(1L);
System.out.println("管理员2取出的价格:" + p2.getPrice());
//管理员1将价格提高60
p1.setPrice(p1.getPrice() + 60);
int res = productMapper.updateById(p1);
System.out.println("管理员1修改的结果:" + res);
//管理员2将价格降低20
p2.setPrice(p2.getPrice() - 20);
int i = productMapper.updateById(p2);
System.out.println("管理员2修改的结果:" + i);
//最终价格
Integer price = productMapper.selectById(1L).getPrice();
System.out.println("最终价格:" + price);

}

原价格为100,理想价格应该是被修改为140,实际上却被修改为80

image-20231101142809951

6.修改实体类

添加@Version注解

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Product {
@TableId
private Long id;
private String name;
private Integer price;
@Version
private Integer version;
}

7.添加乐观锁插件

interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());

@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(){
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}

8.再次测试

先手动将价格重新设置回100

@Autowired
private ProductMapper productMapper;

@Test
void test() {
//管理员1
Product p1 = productMapper.selectById(1L);
System.out.println("管理员1取出的价格:" + p1.getPrice());
//管理员2
Product p2 = productMapper.selectById(1L);
System.out.println("管理员2取出的价格:" + p2.getPrice());
//管理员1将价格提高60
p1.setPrice(p1.getPrice() + 60);
int res = productMapper.updateById(p1);
System.out.println("管理员1修改的结果:" + res);
//管理员2将价格降低20
p2.setPrice(p2.getPrice() - 20);
int i = productMapper.updateById(p2);
System.out.println("管理员2修改的结果:" + i);
//最终价格
Integer price = productMapper.selectById(1L).getPrice();
System.out.println("最终价格:" + price);

}

最终价格只是被修改为160,第二次修改执行失败

image-20231101143803557

乐观锁实现流程

乐观锁实现方式:

  • 取出记录时,获取当前 version
  • 更新时,带上这个 version
  • 执行更新时, set version = newVersion where version = oldVersion
  • 如果 version 不对,就更新失败

image-20231101144114056