Java中synchronized关键字与ReentrantLock实现扣减库存

文章目录

  • 一、前言
  • 二、synchronized关键字
    • 数据准备
    • 环境搭建
      • 依赖
      • yml配置
      • 启动类
      • mapper
      • 实体类
      • controller类
      • service接口
      • serviceimpl实现接口
    • 不加synchronized测试
    • 加上关键字synchronized测试
    • 扣减库存
  • 三、ReentrantLock锁
  • 四 总结

一、前言

本篇讲解单体应用中实现扣减库存,分别使用synchronized和ReentrantLock实现锁,不会出现扣除超卖的现象。

二、synchronized关键字

数据准备

库存表

-- test.stock definition

CREATE TABLE `stock` (
  `stock_id` int NOT NULL AUTO_INCREMENT COMMENT '库存主键',
  `product_id` int NOT NULL COMMENT '商品ID',
  `stock_num` int DEFAULT NULL COMMENT '库存数量',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `create_by` varchar(100) DEFAULT NULL COMMENT '创建人',
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间',
  `update_by` varchar(100) DEFAULT NULL COMMENT '更新人',
  PRIMARY KEY (`stock_id`),
  KEY `stock_product_id_IDX` (`product_id`) USING BTREE,
  CONSTRAINT `stock_FK` FOREIGN KEY (`product_id`) REFERENCES `product` (`product_id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='库存表';

数据

INSERT INTO test.stock
(stock_id, product_id, stock_num, create_time, create_by, update_time, update_by)
VALUES(6, 6, 5000, '2022-09-06 15:19:18', 'elite', '2022-09-06 15:19:18', 'elite');

环境搭建

依赖

  <dependencies>
    
    <dependency>
      <groupId>org.springframework.bootgroupId>
      <artifactId>spring-boot-starter-webartifactId>
    dependency>
    
    <dependency>
      <groupId>com.baomidougroupId>
      <artifactId>mybatis-plus-boot-starterartifactId>
      <version>3.4.1version>
    dependency>
    <dependency>
      <groupId>commons-langgroupId>
      <artifactId>commons-langartifactId>
      <version>2.6version>
    dependency>
    
    <dependency>
      <groupId>com.alibabagroupId>
      <artifactId>druidartifactId>
      <version>1.2.8version>
    dependency>
    <dependency>
      <groupId>cn.hutoolgroupId>
      <artifactId>hutool-allartifactId>
      <version>5.1.4version>
    dependency>
    
    <dependency>
      <groupId>com.alibabagroupId>
      <artifactId>fastjsonartifactId>
      <version>1.2.76version>
    dependency>

    
    <dependency>
      <groupId>mysqlgroupId>
      <artifactId>mysql-connector-javaartifactId>
      <version>8.0.27version>
    dependency>

    <dependency>
      <groupId>junitgroupId>
      <artifactId>junitartifactId>
      <version>4.11version>
      <scope>testscope>
    dependency>
    <dependency>
      <groupId>org.projectlombokgroupId>
      <artifactId>lombokartifactId>
    dependency>
  dependencies>

yml配置

server:
  port: 8899
##配置应用的名字
spring:
  application:
    name: CurrentcyLockApplication
  profiles:
    active: dev
  ##配置数据库
  datasource:
    ##mysql配置
    type: com.alibaba.druid.pool.DruidDataSource
    url: jdbc:mysql://ip:3306/test
    driver-class-name: com.mysql.jdbc.Driver
    username: root
    password: 123456

启动类

@SpringBootApplication
public class CurrentcyLockApplication
{
    public static void main( String[] args )
    {
        SpringApplication.run(CurrentcyLockApplication.class,args);
    }
}

mapper

/**
 * 库存映射类
 */
@Mapper
public interface StockMapper extends BaseMapper<Stock> {
    @Select("SELECT stock_id, product_id, stock_num, create_time, create_by, update_time, update_by\n" +
            "FROM test.stock where product_id = #{productId}")
    Stock getStockByProductId(Integer productId);
}

实体类

@Data
@EqualsAndHashCode(callSuper = false)
@TableName("`stock`")
public class Stock {
    /**
     * 用户ID
     */
    @TableId(value = "stock_id", type = IdType.AUTO)
    private Integer stockId;

    /**
     * 商品ID
     */
    @TableField(value = "product_id")
    private Integer productId;
    /**
     * 库存数量
     */
    @TableField(value = "stock_num")
    private Integer stockNum;


    //创建人
    @TableField(value = "create_by")
    private String createBy;
    //创建时间
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(value = "create_time",fill = FieldFill.INSERT)
    private String createTime;
    //更新人
    @TableField(value = "update_by")
    private String updateBy;
    //更新时间
    @JsonFormat(timezone = "GMT+8", pattern = "yyyy-MM-dd HH:mm:ss")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(value = "update_time",fill = FieldFill.INSERT_UPDATE)
    private String updateTime;
}

controller类

@RestController
@RequestMapping("/stock")
public class StockController {

    @Autowired
    IStockService stockService;
    /**
     * 扣减库存
     * @return
     */
    @GetMapping("/deductStock/{productId}")
    public String deductStock(@PathVariable("productId") Integer productId){
        return stockService.deductStock(productId);
    }
}

service接口

/**
 * 库存处理的逻辑
 */
public interface IStockService extends IService<Stock> {
    String deductStock(Integer productId);
}

serviceimpl实现接口

@Service
@Slf4j
public class IStockServiceImpl  extends ServiceImpl<StockMapper, Stock> implements IStockService {

    @Autowired
    StockMapper stockMapper;
    /**
     * 扣减库存
     * @return
     */
    @Override
    public String deductStock(Integer productId) {
       log.info("扣减库存开始......");
       Stock stock = stockMapper.getStockByProductId(productId);
         log.info("当前库存量:" + stock.getStockNum());
         //扣减库存
         if (stock.getStockNum() > 0) {
             //默认扣减1
             stock.setStockNum(stock.getStockNum() - 1);
             stockMapper.updateById(stock);
         } else {
             log.info("扣减库存失败,无库存可用......");
             return "扣减库存失败";
         }
       return "ok";
    }
}

不加synchronized测试

库存5000,使用jemeter发送100个线程循环50次进行扣减库存。
Java中synchronized关键字与ReentrantLock实现扣减库存_第1张图片
此时库存4863,出现了超卖的情况。

加上关键字synchronized测试

方法上加上关键字synchronized

 public synchronized  String deductStock(Integer productId) 

Java中synchronized关键字与ReentrantLock实现扣减库存_第2张图片
synchronized 了独占的排他锁,只能一个一个线程进行执行。

Java中synchronized关键字与ReentrantLock实现扣减库存_第3张图片

三、ReentrantLock锁

    private final ReentrantLock lock = new ReentrantLock();
    /**
     * 扣减库存
     * @return
     */
    @Override
    public /*synchronized*/  String deductStock(Integer productId) {
        log.info("扣减库存开始......");
        //幂等性

        //尝试获取锁:
        // 成功获取则立即返回true,获取失败则立即返回false。不必一直等待锁的释放
        if (lock.tryLock()) {
           try {
               //处理重复通知
               //接口调用的幂等性:无论接口被调用多少次,以下业务执行一次
               //获取库存数量
               Stock stock = stockMapper.getStockByProductId(productId);
               log.info("当前库存量:" + stock.getStockNum());
               //扣减库存
               if (stock.getStockNum() > 0) {
                   //默认扣减1
                   stock.setStockNum(stock.getStockNum() - 1);
                   stockMapper.updateById(stock);

               } else {
                   log.info("扣减库存失败,无库存可用......");
                   return "扣减库存失败";
               }
           }finally {
               lock.unlock();
          }
        }
       return "ok";

ReentrantLock是可重入锁,请不到锁的情况下返回false,不必一直等待。

四 总结

本篇讲解都是JDk自身提供的工具来实现锁,对于分布式部署的服务并不适用。分布式服务需要用分布式锁来实现,如数据库来实现,或者redis来实现分布式锁。

你可能感兴趣的:(Java,java,mybatis,synchronized,reentrantLock)