Spring Boot中并发问题的简单处理

       解决多个用户同时访问一个接口可能产生并发问题,下面用一个生成编号的接口做示例。并发情况下可能生成相同编号。

一、使用同步机制

(一)使用 synchronized 关键字

如果是单实例部署,可以使用 synchronized 关键字来保证代码块的同步执行。例如:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SampleController {

    private int counter = 0;

    @GetMapping("/generateId")
    public synchronized String generateId() {
        // 生成编号的逻辑
        counter++;
        return "ID-" + counter;
    }
}

解释:

  • @GetMapping("/generateId"):将 generateId 方法映射到 /generateId 路径。
  • synchronized:保证同一时刻只有一个线程可以执行 generateId 方法,避免多个线程同时执行生成编号的逻辑,防止生成相同的编号。

(二)使用 ReentrantLock

使用 ReentrantLock 可以实现更灵活的锁控制:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SampleController {

    private int counter = 0;
    private final Lock lock = new ReentrantLock();

    @GetMapping("/generateId")
    public String generateId() {
        lock.lock();
        try {
            // 生成编号的逻辑
            counter++;
            return "ID-" + counter;
        } finally {
            lock.unlock();
        }
    }
}

解释:

  • private final Lock lock = new ReentrantLock();:创建一个可重入锁。
  • lock.lock();:获取锁。
  • try 块内是临界区,执行生成编号的逻辑。
  • finally 块中 lock.unlock(); 确保锁最终会被释放,防止死锁。

二、使用分布式锁

       对于多实例部署,使用分布式锁可以保证多个实例之间的并发控制。可以使用 Redis 或 ZooKeeper 实现分布式锁。

(一)使用 Redis 实现分布式锁

首先,添加 Redis 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

然后,使用 Redis 实现分布式锁:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@RestController
public class SampleController {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @GetMapping("/generateId")
    public String generateId() {
        String lockKey = "generateIdLock";
        String requestId = UUID.randomUUID().toString();
        try {
            Boolean success = redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, 10, TimeUnit.SECONDS);
            if (success == null ||!success) {
                throw new RuntimeException("Failed to acquire lock");
            }
            // 生成编号的逻辑
            int counter = (int) redisTemplate.opsForValue().increment("counter", 1);
            return "ID-" + counter;
        } finally {
            if (requestId.equals(redisTemplate.opsForValue().get(lockKey))) {
                redisTemplate.delete(lockKey);
            }
        }
    }
}

解释:

  • @Autowired StringRedisTemplate redisTemplate;:注入 Redis 操作模板。
  • String lockKey = "generateIdLock";:定义锁的键。
  • String requestId = UUID.randomUUID().toString();:生成一个唯一的请求 ID。
  • redisTemplate.opsForValue().setIfAbsent(lockKey, requestId, 10, TimeUnit.SECONDS);:尝试设置锁,设置过期时间为 10 秒。
  • redisTemplate.opsForValue().increment("counter", 1);:使用 Redis 的原子操作生成唯一编号。
  • 在 finally 块中,检查锁是否属于当前请求,若是则删除锁,防止死锁。

(二)使用 Redisson 实现分布式锁

添加 Redisson 依赖:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.17.0</version>
</dependency>

使用 Redisson 实现分布式锁:

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SampleController {

    @Autowired
    private RedissonClient redissonClient;

    @GetMapping("/generateId")
    public String generateId() {
        RLock lock = redissonClient.getLock("generateIdLock");
        try {
            lock.lock();
            // 生成编号的逻辑
            int counter = (int) redisTemplate.opsForValue().increment("counter", 1);
            return "ID-" + counter;
        } finally {
            lock.unlock();
        }
    }
}

解释:

  • @Autowired RedissonClient redissonClient;:注入 Redisson 客户端。
  • RLock lock = redissonClient.getLock("generateIdLock");:获取分布式锁。
  • lock.lock();:加锁。
  • 在 finally 块中 lock.unlock(); 解锁。

三、使用数据库的唯一约束

可以利用数据库的唯一约束来保证编号的唯一性。

(一)使用数据库的自增主键

如果使用数据库(如 MySQL)存储编号,可以使用自增主键:

CREATE TABLE ids ( id INT AUTO_INCREMENT PRIMARY KEY, data VARCHAR(255) );

在 Spring Boot 中:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SampleController {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @GetMapping("/generateId")
    public String generateId() {
        int generatedId = jdbcTemplate.queryForObject("INSERT INTO ids (data) VALUES ('example') RETURNING id", Integer.class);
        return "ID-" + generatedId;
    }
}

解释:

  • @Autowired JdbcTemplate jdbcTemplate;:注入 JdbcTemplate
  • jdbcTemplate.queryForObject("INSERT INTO ids (data) VALUES ('example') RETURNING id", Integer.class);:插入数据并返回自增的 id

(二)使用 UUID 作为编号

使用 UUID 可以避免重复编号,无需加锁:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.UUID;

@RestController
public class SampleController {

    @GetMapping("/generateId")
    public String generateId() {
        return UUID.randomUUID().toString();
    }
}

解释:

  • UUID.randomUUID().toString();:生成一个唯一的 UUID 作为编号。

四、使用并发容器

使用 AtomicInteger 等并发容器:

import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class SampleController {

    private final AtomicInteger counter = new AtomicInteger(0);

    @GetMapping("/generateId")
    public String generateId() {
        return "ID-" + counter.incrementAndGet();
    }
}

解释:

  • private final AtomicInteger counter = new AtomicInteger(0);:使用 AtomicInteger 保证原子操作。
  • counter.incrementAndGet();:原子性地增加并获取计数器的值。

总结

  • 对于单实例部署,可以使用 synchronized 关键字或 ReentrantLock 进行同步。
  • 对于多实例部署,推荐使用分布式锁(Redis、Redisson)或数据库的唯一约束(自增主键、唯一索引)。
  • 使用 UUID 可以简单有效地生成唯一编号,但不适合有序编号的场景。
  • 使用并发容器可以保证原子操作,适用于简单的计数器场景。

       根据具体的业务需求和部署架构,选择合适的方法可以有效地解决并发问题,避免生成相同的编号。在高并发环境下,分布式锁和数据库的唯一约束通常是更可靠的解决方案,但需要考虑性能和系统复杂度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值