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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
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来实现分布式锁。

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
12天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
12天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
13天前
|
缓存 安全 Java
Java volatile关键字:你真的懂了吗?
`volatile` 是 Java 中的轻量级同步机制,主要用于保证多线程环境下共享变量的可见性和防止指令重排。它确保一个线程对 `volatile` 变量的修改能立即被其他线程看到,但不能保证原子性。典型应用场景包括状态标记、双重检查锁定和安全发布对象等。`volatile` 适用于布尔型、字节型等简单类型及引用类型,不适用于 `long` 和 `double` 类型。与 `synchronized` 不同,`volatile` 不提供互斥性,因此在需要互斥的场景下不能替代 `synchronized`。
2105 3
|
13天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
35 3
|
2月前
|
JavaScript 前端开发 Java
java中的this关键字
欢迎来到我的博客,我是瑞雨溪,一名热爱JavaScript与Vue的大一学生。自学前端2年半,正向全栈进发。若我的文章对你有帮助,欢迎关注,持续更新中!🎉🎉🎉
56 9
|
2月前
|
设计模式 JavaScript 前端开发
java中的static关键字
欢迎来到瑞雨溪的博客,博主是一名热爱JavaScript和Vue的大一学生,致力于全栈开发。如果你从我的文章中受益,欢迎关注我,将持续分享更多优质内容。你的支持是我前进的动力!🎉🎉🎉
56 8
|
2月前
|
Java 开发者
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
在Java多线程编程的世界里,Lock接口正逐渐成为高手们的首选,取代了传统的synchronized关键字
49 4
|
6月前
|
存储 安全 Java
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
55 0
|
6月前
|
安全 Java 开发者
Java多线程:synchronized关键字和ReentrantLock的区别,为什么我们可能需要使用ReentrantLock而不是synchronized?
Java多线程:synchronized关键字和ReentrantLock的区别,为什么我们可能需要使用ReentrantLock而不是synchronized?
69 0
|
8月前
|
安全 Java 编译器
Java多线程基础-6:线程安全问题及解决措施,synchronized关键字与volatile关键字(一)
线程安全问题是多线程编程中最典型的一类问题之一。如果多线程环境下代码运行的结果是符合我们预期的,即该结果正是在单线程环境中应该出现的结果,则说这个程序是线程安全的。 通俗来说,线程不安全指的就是某一代码在多线程环境下执行会出现bug,而在单线程环境下执行就不会。线程安全问题本质上是由于线程之间的调度顺序的不确定性,正是这样的不确定性,给我们的代码带来了很多“变数”。 本文将对Java多线程编程中,线程安全问题展开详细的讲解。
110 0