课程报名人数限制是教育培训类系统的常见需求,核心目标是确保报名总人数不超过课程设定的最大容量,同时要处理多用户同时报名的并发场景,避免出现超报问题。下面先介绍基础的实现思路,再逐步优化到适配高并发的方案。

基础实现:使用普通计数器
最基础的思路是维护一个当前报名人数的计数器,每次报名时先判断当前人数是否小于最大限制,再执行加一操作。这种方式在单线程场景下可以正常工作,但并发场景下会出现问题。
public class CourseEnroll {
// 课程最大报名人数
private int maxCount = 100;
// 当前报名人数
private int currentCount = 0;
public boolean enroll() {
if (currentCount < maxCount) {
// 模拟业务处理耗时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
currentCount++;
return true;
}
return false;
}
public int getCurrentCount() {
return currentCount;
}
}
并发问题说明
当两个线程同时判断currentCount < maxCount为true时,都会执行后续的加一操作,最终导致报名人数超过最大限制。比如最大限制是100,两个线程同时进入判断后,最终currentCount会变成102,出现超报情况。
方案一:使用synchronized关键字实现同步
synchronized是Java内置的同步机制,可以保证同一时间只有一个线程执行被修饰的代码块,避免并发冲突。我们可以对报名方法进行同步控制。
public class CourseEnrollSync {
private int maxCount = 100;
private int currentCount = 0;
// 同步方法,同一时间只有一个线程可以执行
public synchronized boolean enroll() {
if (currentCount < maxCount) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
currentCount++;
return true;
}
return false;
}
public int getCurrentCount() {
return currentCount;
}
}
这种方式虽然解决了并发问题,但synchronized是重量级锁,高并发场景下性能较差,所有报名请求会排队执行,响应速度会变慢。
方案二:使用AtomicInteger实现原子操作
AtomicInteger是java.util.concurrent.atomic包下的原子类,提供了原子性的自增、判断等操作,性能比synchronized更好,适合高并发场景。
import java.util.concurrent.atomic.AtomicInteger;
public class CourseEnrollAtomic {
private int maxCount = 100;
// 原子类型的当前报名人数
private AtomicInteger currentCount = new AtomicInteger(0);
public boolean enroll() {
// 循环尝试报名,直到成功或者人数已满
while (true) {
int nowCount = currentCount.get();
if (nowCount >= maxCount) {
return false;
}
// 原子性地比较并自增,如果当前值还是nowCount,就自增1,返回自增前的值
if (currentCount.compareAndSet(nowCount, nowCount + 1)) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
// 如果compareAndSet失败,说明有其他线程修改了currentCount,重新循环尝试
}
}
public int getCurrentCount() {
return currentCount.get();
}
}
compareAndSet是CAS(比较并交换)操作,是乐观锁的实现,不需要阻塞线程,性能比synchronized更好,适合高并发的报名场景。
方案三:结合数据库实现持久化限制
上面的方案都是内存中的计数器,系统重启后数据会丢失,实际开发中通常需要结合数据库实现持久化的报名限制。我们可以通过数据库的乐观锁或者行锁来控制人数。
数据库表结构示例:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 课程ID |
| course_name | varchar | 课程名称 |
| max_enroll | int | 最大报名人数 |
| current_enroll | int | 当前报名人数 |
| version | int | 版本号,用于乐观锁 |
使用乐观锁的更新SQL示例:
UPDATE course SET current_enroll = current_enroll + 1, version = version + 1 WHERE id = ? AND current_enroll < max_enroll AND version = ?
Java中执行该SQL后,判断受影响行数,如果大于0说明报名成功,否则说明人数已满或者版本号冲突,需要重试或者返回失败。
不同方案的选择建议
- 如果是单机、低并发的临时场景,可以使用synchronized或者AtomicInteger的内存计数器方案
- 如果是分布式系统或者需要持久化报名数据,建议结合数据库的乐观锁方案,同时可以搭配Redis缓存计数器提升性能
- 高并发场景下优先选择AtomicInteger或者数据库CAS操作,避免使用重量级锁
Java课程报名限制并发控制synchronizedAtomicInteger修改时间:2026-06-28 19:27:41