Java中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>
    <!--web-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <!--mybtais-plus-->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.4.1</version>
    </dependency>
    <dependency>
      <groupId>commons-lang</groupId>
      <artifactId>commons-lang</artifactId>
      <version>2.6</version>
    </dependency>
    <!--druid数据源-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.8</version>
    </dependency>
    <dependency>
      <groupId>cn.hutool</groupId>
      <artifactId>hutool-all</artifactId>
      <version>5.1.4</version>
    </dependency>
    <!--fastjson-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.76</version>
    </dependency>

    <!--mysql连接包-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.27</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
    </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次进行扣减库存。
库存剩余量
此时库存4863,出现了超卖的情况。

加上关键字synchronized测试

方法上加上关键字synchronized

 public synchronized  String deductStock(Integer productId) 

可以看到库存为0
synchronized 了独占的排他锁,只能一个一个线程进行执行。

扣减库存

三、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来实现分布式锁。

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小刘同学要加油呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值