适合做「有真实并发痛点」的中小型业务系统,而非纯玩具项目;伙伴匹配系统因天然存在抢人、跨表写入、Redis非原子操作、定时任务与前台竞争等典型并发问题,比用户中心更适合作为练手项目。直接说结论:**适合做「有真实并发痛点」的中小型业务系统,而不是纯玩具项目**。练手项目必须能触发竞争、暴露锁失效、线程泄漏、超卖、计数不准等典型问题,否则练了也白练。
用户中心项目(注册/登录/改密)本质是 CRUD,加个 synchronized 或 ReentrantLock 就能糊弄过去,根本压不出并发问题。
而伙伴匹配系统天然带以下并发场景:
INCR 和 ZADD 组合操作不是原子的,得靠 Lua 脚本或分布式锁兜底SELECT FOR UPDATE 或乐观锁就会丢数据单机用 synchronized 没问题,但一上 Docker 或集群,库存扣减立刻超卖。原因很简单:synchronized 锁的是 JVM 内对象,不同实例之间完全不感知。
实操建议:
"stock:goodsId:10086",绝不能写成 "StockService.deduct()" 这种全局限定符finally 块里调用 unlock(),否则节点宕机就永久死锁lock.lock(3, TimeUnit.SECONDS),避免客户端网络卡顿导致锁霸占太久RLock 自动续期机制很香,但别依赖它——超时时间仍要按业务容忍度设(比如下单流程最长 2 秒)RedissionClient.getLock("xxx") 示例,再往业务里塞。
Executors.newFixedThreadPool() 看似省事,但它的 LinkedBlockingQueue 默认无界,高并发下任务堆积会 OOM。真实项目必须自己控参:
2 × CPU
ArrayBlockingQueue(有界),容量设为预估峰值 QPS × 平均处理耗时(单位秒)CallerRunsPolicy,让调用线程自己执行,比 AbortPolicy 丢任务更可控/actuator/metrics/jvm.threads.live 和自定义线程池指标必须接入 Prometheus,不然等于没配。
真正卡住人的从来不是“怎么写锁”,而是“什么时候不该用锁”——比如用 ConcurrentHashMap 替代 HashMap + synchroniz
ed,用 LongAdder 替代 AtomicLong 在高争用场景,或者直接用消息队列削峰。这些取舍点,只在压测到 CPU 100%、GC 频繁、线程阻塞率飙升时才看得清。