当前位置: 首页 > news >正文

演示synchronized锁机制用法的简单Demo

演示synchronized锁机制用法的简单Demo。我们以"银行开户"场景为例:每个用户只能创建一个账户(模拟类似原代码中每个用户只能有一个私有空间的限制)。

第1步:创建项目结构

demo-lock
├── src/main/java/com/example/demo/
│   ├── controller/AccountController.java
│   ├── entity/Account.java
│   ├── mapper/AccountMapper.java
│   ├── service/AccountService.java
│   └── DemoApplication.java
└── src/main/resources/
    └── application.yml

第2步:添加依赖(pom.xml)

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.3.1</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
</dependencies>

第3步:实体类 Account.java

@Data
@TableName("t_account")
public class Account {
    @TableId(type = IdType.AUTO)
    private Long id;
    private Long userId;
    private BigDecimal balance;
}

第4步:Mapper接口 AccountMapper.java

public interface AccountMapper extends BaseMapper<Account> {
}

第5步:Service层 AccountService.java

@Service
@RequiredArgsConstructor
public class AccountService {
    private final AccountMapper accountMapper;

    // 无锁版本(存在并发问题)
    public void createAccountUnsafe(Long userId) {
        Long count = accountMapper.selectCount(new QueryWrapper<Account>().eq("user_id", userId));
        if (count > 0) {
            throw new RuntimeException("用户已存在账户");
        }
        
        Account account = new Account();
        account.setUserId(userId);
        account.setBalance(BigDecimal.ZERO);
        accountMapper.insert(account);
    }

    // 有锁版本(线程安全)
    public void createAccountWithLock(Long userId) {
        String lockKey = String.valueOf(userId).intern();
        
        synchronized (lockKey) {
            createAccountUnsafe(userId);
        }
    }
}

第6步:Controller层 AccountController.java

@RestController
@RequiredArgsConstructor
public class AccountController {
    private final AccountService accountService;

    // 不安全的开户接口(用于演示并发问题)
    @GetMapping("/unsafe/{userId}")
    public String unsafeCreate(@PathVariable Long userId) {
        try {
            accountService.createAccountUnsafe(userId);
            return "开户成功";
        } catch (Exception e) {
            return e.getMessage();
        }
    }

    // 安全的开户接口(使用synchronized锁)
    @GetMapping("/safe/{userId}")
    public String safeCreate(@PathVariable Long userId) {
        try {
            accountService.createAccountWithLock(userId);
            return "开户成功";
        } catch (Exception e) {
            return e.getMessage();
        }
    }
}

第7步:配置文件 application.yml

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/demo?useSSL=false&characterEncoding=utf8
    username: root
    password: your_password

mybatis-plus:
  configuration:
    map-underscore-to-camel-case: true

第8步:测试步骤

  1. 初始化数据库
CREATE DATABASE IF NOT EXISTS demo;
USE demo;

CREATE TABLE t_account (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id BIGINT NOT NULL UNIQUE,
    balance DECIMAL(10,2) NOT NULL DEFAULT 0
);
  1. 启动应用
mvn spring-boot:run
  1. 并发测试(使用JMeter或Postman)

测试不安全接口

  • 用多个线程同时调用 GET http://localhost:8080/unsafe/123
  • 可能结果:成功创建多个账户(违反唯一约束)

测试安全接口

  • 用多个线程同时调用 GET http://localhost:8080/safe/456
  • 结果:只会有第一个请求成功创建账户

关键代码解释

  1. 锁对象的选择
String lockKey = String.valueOf(userId).intern();
  • intern()保证相同userId值返回同一个String对象(来自字符串常量池)
  • 不同userId对应的锁对象不同,实现细粒度锁
  1. 同步代码块
synchronized (lockKey) {
    // 临界区代码
}
  • 确保同一用户的并发请求串行执行
  • 不同用户的请求可以并行处理

典型输出对比

无锁接口测试结果

第一次请求:开户成功(创建账户)
第二次请求:Duplicate entry '123' for key 'user_id'(违反唯一约束)

有锁接口测试结果

第一次请求:开户成功(创建账户)
后续所有请求:用户已存在账户(业务校验拦截)

总结说明表格

关键点说明
synchronized范围基于用户ID的细粒度锁,不影响其他用户操作
String.intern()保证相同userid得到的String是同一个对象(来自字符串常量池)
事务边界在锁范围内包含整个事务操作(确保查询和插入操作的原子性)
性能影响只对相同用户的并发请求串行化处理,不影响不同用户的并发处理
适用场景需要基于特定维度(如用户ID)进行并发控制的场景

可以通过这个Demo逐步体验:

  1. 先观察不加锁时的并发问题
  2. 再体验加锁后的线程安全效果
  3. 最后尝试调整userId观察不同用户的并发情况

Jmeter测试

  1. 设置 HTTP 请求
    在这里插入图片描述

  2. 设置线程组
    在这里插入图片描述

  3. 添加 查看结果树
    在这里插入图片描述

  4. 运行
    在这里插入图片描述

相关文章:

  • 网络工程师 (39)常见广域网技术
  • Typescript 【详解】配置文件 tsconfig.json
  • aws(学习笔记第二十八课) aws eks使用练习(hands on)
  • Rook-ceph(1.92最新版)
  • Flappy Bird开发学习记录
  • 【Linux】详谈 进程控制
  • 机器学习:二分类和多分类
  • 安卓逆向(Bundle)
  • 把 CSV 文件摄入到 Elasticsearch 中 - CSVES
  • PAT乙级真题 — 1084 外观数列(java)
  • 一口井深7米,一只蜗牛从井底往上爬每天爬3米掉下去1米,问几天能爬上井口?
  • CEF132 编译指南 Linux 篇 - 获取 CEF 源代码:源码同步详解(五)
  • 代码随想录算法训练营Day47
  • 爱彼(Audemars Piguet):瑞士制表艺术的巅峰之作(中英双语)
  • 使用Charles进行mock请求
  • 如何调整 Nginx工作进程数以提升性能
  • 华为ensp IPSEC隧道两端经过nat配置实验!
  • 【kafka系列】Exactly Once语义
  • DeepSeek进阶开发与应用2:DeepSeek中的自定义层与复杂模型构建
  • 【AI】Docker中快速部署Ollama并安装DeepSeek-R1模型: 一步步指南
  • 硅料收储挺价“小作文”发酵光伏板块罕见大涨,知情人士:确实在谈
  • 沈阳卫健委通报“健康证”办理乱象:涉事医院已被立案查处
  • 走进“双遗之城”,领略文武风采:沧州何以成文旅新贵
  • 训练孩子的科学思维,上海虹口推出“六个一百”旗舰工程
  • 甩掉“肥胖刺客”,科学减重指南来了
  • 75万采购防火墙实为299元路由器?重庆三峡学院发布终止公告:出现违法违规行为