适合做「有真实并发痛点」的中小型业务系统,而非纯玩具项目;伙伴匹配系统因天然存在抢人、跨表写入、Redis非原子操作、定时任务与前台竞争等典型并发问题,比用户中心更适合作为练手项目。

为什么选「伙伴匹配系统」而不是「用户中心」?
用户中心项目(注册/登录/改密)本质是 CRUD,加个 synchronized 或 ReentrantLock 就能糊弄过去,根本压不出并发问题。
而伙伴匹配系统天然带以下并发场景:
- 多个用户同时发起「找搭子」请求,需从同一候选人池中互斥抢人
- 匹配结果需原子写入双方关系表 + 更新状态,跨库/跨表易出脏写
- 用 Redis 缓存热度榜单时,
INCR和ZADD组合操作不是原子的,得靠 Lua 脚本或分布式锁兜底 - 后台定时任务清理过期匹配请求,和前台匹配逻辑共用同一张表,没
SELECT FOR UPDATE或乐观锁就会丢数据
秒杀类项目必须配 Redisson 分布式锁,别碰 synchronized
单机用 synchronized 没问题,但一上 Docker 或集群,库存扣减立刻超卖。原因很简单:synchronized 锁的是 JVM 内对象,不同实例之间完全不感知。
实操建议:
- 锁键必须是业务粒度,比如
"stock:goodsId:10086",绝不能写成"StockService.deduct()"这种全局限定符 - 一定要在
finally块里调用unlock(),否则节点宕机就永久死锁 - 加锁时务必设超时,例如
lock.lock(3, TimeUnit.SECONDS),避免客户端网络卡顿导致锁霸占太久 - Redisson 的
RLock自动续期机制很香,但别依赖它——超时时间仍要按业务容忍度设(比如下单流程最长 2 秒)
RedissionClient.getLock("xxx") 示例,再往业务里塞。
线程池别用 Executors 工厂方法,必须手写 ThreadPoolExecutor
Executors.newFixedThreadPool() 看似省事,但它的 LinkedBlockingQueue 默认无界,高并发下任务堆积会 OOM。真实项目必须自己控参:
- 核心线程数 = CPU 核数 × (1 + 平均等待时间 / 平均工作时间),Web 服务通常设为
2 × CPU - 最大线程数别盲目拉高,超过 200 很可能把 DB 连接池打爆
- 队列类型优先选
ArrayBlockingQueue(有界),容量设为预估峰值 QPS × 平均处理耗时(单位秒) - 拒绝策略用
CallerRunsPolicy,让调用线程自己执行,比AbortPolicy丢任务更可控
/actuator/metrics/jvm.threads.live 和自定义线程池指标必须接入 Prometheus,不然等于没配。
真正卡住人的从来不是“怎么写锁”,而是“什么时候不该用锁”——比如用 ConcurrentHashMap 替代 HashMap + synchronized,用 LongAdder 替代 AtomicLong 在高争用场景,或者直接用消息队列削峰。这些取舍点,只在压测到 CPU 100%、GC 频繁、线程阻塞率飙升时才看得清。











