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

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 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>

   <!--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次进行扣减库存。

4c27b1f94b114fc5b1334a720787a6a0.png

此时库存4863,出现了超卖的情况。

加上关键字synchronized测试

方法上加上关键字synchronized

public synchronized  String deductStock(Integer productId)  

12f12fd7fd3a4669b495d236855e5a12.png

synchronized 了独占的排他锁,只能一个一个线程进行执行。

b58b118e51094525b05dbf304b0b4f67.png

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

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
Java 开发者 C++
Java多线程同步大揭秘:synchronized与Lock的终极对决!
Java多线程同步大揭秘:synchronized与Lock的终极对决!
60 5
|
1月前
|
存储 安全 Java
Java并发编程之深入理解Synchronized关键字
在Java的并发编程领域,synchronized关键字扮演着守护者的角色。它确保了多个线程访问共享资源时的同步性和安全性。本文将通过浅显易懂的语言和实例,带你一步步了解synchronized的神秘面纱,从基本使用到底层原理,再到它的优化技巧,让你在编写高效安全的多线程代码时更加得心应手。
|
1月前
|
缓存 Java 编译器
JAVA并发编程synchronized全能王的原理
本文详细介绍了Java并发编程中的三大特性:原子性、可见性和有序性,并探讨了多线程环境下可能出现的安全问题。文章通过示例解释了指令重排、可见性及原子性问题,并介绍了`synchronized`如何全面解决这些问题。最后,通过一个多窗口售票示例展示了`synchronized`的具体应用。
|
2月前
|
Java
不懂synchronized?那你可能错过了Java的“半壁江山”!
不懂synchronized?那你可能错过了Java的“半壁江山”!
52 6
|
2月前
|
安全 Java 开发者
Java多线程同步:synchronized与Lock的“爱恨情仇”!
Java多线程同步:synchronized与Lock的“爱恨情仇”!
82 5
|
2月前
|
Java 开发者
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选
在 Java 多线程编程中,Lock 接口正逐渐取代传统的 `synchronized` 关键字,成为高手们的首选。相比 `synchronized`,Lock 提供了更灵活强大的线程同步机制,包括可中断等待、超时等待、重入锁及读写锁等高级特性,极大提升了多线程应用的性能和可靠性。通过示例对比,可以看出 Lock 接口通过 `lock()` 和 `unlock()` 明确管理锁的获取和释放,避免死锁风险,并支持公平锁选择和条件变量,使其在高并发场景下更具优势。掌握 Lock 接口将助力开发者构建更高效、可靠的多线程应用。
21 2
|
2月前
|
Java 测试技术
Java多线程同步实战:从synchronized到Lock的进化之路!
Java多线程同步实战:从synchronized到Lock的进化之路!
89 1
|
21天前
|
Java
JAVA并发编程ReentrantLock核心原理剖析
本文介绍了Java并发编程中ReentrantLock的重要性和优势,详细解析了其原理及源码实现。ReentrantLock作为一种可重入锁,弥补了synchronized的不足,如支持公平锁与非公平锁、响应中断等。文章通过源码分析,展示了ReentrantLock如何基于AQS实现公平锁和非公平锁,并解释了两者的具体实现过程。
|
2月前
|
传感器 C# 监控
硬件交互新体验:WPF与传感器的完美结合——从初始化串行端口到读取温度数据,一步步教你打造实时监控的智能应用
【8月更文挑战第31天】本文通过详细教程,指导Windows Presentation Foundation (WPF) 开发者如何读取并处理温度传感器数据,增强应用程序的功能性和用户体验。首先,通过`.NET Framework`的`Serial Port`类实现与传感器的串行通信;接着,创建WPF界面显示实时数据;最后,提供示例代码说明如何初始化串行端口及读取数据。无论哪种传感器,只要支持串行通信,均可采用类似方法集成到WPF应用中。适合希望掌握硬件交互技术的WPF开发者参考。
43 0
|
2月前
|
安全 Java
Java并发编程实战:使用synchronized和ReentrantLock实现线程安全
【8月更文挑战第31天】在Java并发编程中,保证线程安全是至关重要的。本文将通过对比synchronized和ReentrantLock两种锁机制,深入探讨它们在实现线程安全方面的优缺点,并通过代码示例展示如何使用这两种锁来保护共享资源。