Java拍卖系统竞拍并发问题求解:synchronized与Lock选型疑问
拍卖系统竞拍并发问题解决方案与代码审查
一、synchronized 还是 Lock?
- 如果只是需要基础的互斥控制(保证同一商品的竞拍操作串行执行),
synchronized完全够用。它代码简洁,JDK 1.6及以后版本对其做了偏向锁、轻量级锁等优化,性能并不比Lock类差。 - 如果需要更灵活的锁控制,比如:
- 公平锁(按请求顺序分配锁,避免线程饥饿)
- 可中断的锁等待(防止线程无限阻塞)
- 超时获取锁(避免长时间占用资源)
- 多条件变量(区分竞拍成功/失败的等待队列)
这种场景下ReentrantLock等Lock实现会更合适。
对于拍卖系统的竞拍场景,若无特殊复杂需求,synchronized足以应对并发问题;若追求更高的并发灵活性或需要公平竞拍机制,可以考虑使用Lock。
二、当前Bid方法的问题分析
锁粒度过大:
方法用synchronized修饰,锁的是当前类实例(this),意味着所有商品的竞拍操作都会串行执行,严重影响系统并发能力。不同商品的竞拍是独立的,锁粒度应缩小到Item实例,而非整个服务实例。金额精度问题:
使用double处理金额存在精度丢失风险(比如0.1+0.2≠0.3),金融场景必须用BigDecimal保证精度。异常处理缺失:
unlockMoney和lockMoney操作若抛出异常,会导致数据不一致:比如已解锁上一个竞拍者的余额,但当前竞拍者余额锁定失败时,商品价格已修改、竞拍历史已添加,却无回滚机制。线程安全依赖不明确:
Item的getCurrentPrice/setCurrentPrice、Bidder的getBalance/lockMoney/unlockMoney方法若未做线程安全处理,即使在synchronized块中调用,也可能出现可见性或原子性问题。失败信息模糊:
所有失败仅输出"Bid failed",无法区分是金额低于当前价还是余额不足,不利于排查问题和用户反馈。
三、优化后的代码示例
import java.math.BigDecimal; import java.util.List; public class AuctionService { private List<BidTransaction> bidHistory; private BidTransaction latestBid; public void bid(Item item, Bidder bidder, BigDecimal bidAmount) { // 锁Item实例,仅限制同一商品的竞拍串行,不同商品可并发 synchronized (item) { // 校验竞拍金额是否高于当前价 if (bidAmount.compareTo(item.getCurrentPrice()) <= 0) { System.out.println("Bid failed: Amount must be higher than current price (" + item.getCurrentPrice() + ")"); return; } // 校验竞拍者余额是否充足 if (bidAmount.compareTo(bidder.getBalance()) > 0) { System.out.println("Bid failed: Insufficient balance. Your balance is " + bidder.getBalance()); return; } BidTransaction previousBid = this.latestBid; // 解锁上一个竞拍者的冻结金额 if (previousBid != null) { try { previousBid.getBidder().unlockMoney(previousBid.getBidAmount()); } catch (Exception e) { System.err.println("Failed to unlock money for bidder: " + previousBid.getBidder().getUsername()); return; } } // 创建新竞拍交易 BidTransaction newBid = new BidTransaction(item, bidder, bidAmount); this.bidHistory.add(newBid); this.setLatestBid(newBid); item.setCurrentPrice(bidAmount); // 冻结当前竞拍者的金额,失败则回滚状态 try { bidder.lockMoney(bidAmount); } catch (Exception e) { System.err.println("Failed to lock money for bidder: " + bidder.getUsername()); // 回滚竞拍状态 this.setLatestBid(previousBid); item.setCurrentPrice(previousBid != null ? previousBid.getBidAmount() : BigDecimal.ZERO); this.bidHistory.remove(newBid); return; } System.out.println(bidder.getUsername() + " successfully bid " + bidAmount); } } // 其他必要的getter/setter public void setLatestBid(BidTransaction latestBid) { this.latestBid = latestBid; } } // 线程安全的Item类 class Item { private BigDecimal currentPrice; public synchronized BigDecimal getCurrentPrice() { return currentPrice; } public synchronized void setCurrentPrice(BigDecimal currentPrice) { this.currentPrice = currentPrice; } } // 线程安全的Bidder类 class Bidder { private BigDecimal balance; private String username; public synchronized BigDecimal getBalance() { return balance; } public synchronized void lockMoney(BigDecimal amount) throws Exception { if (balance.compareTo(amount) < 0) { throw new Exception("Insufficient balance"); } this.balance = balance.subtract(amount); } public synchronized void unlockMoney(BigDecimal amount) { this.balance = balance.add(amount); } public String getUsername() { return username; } } class BidTransaction { private Item item; private Bidder bidder; private BigDecimal bidAmount; public BidTransaction(Item item, Bidder bidder, BigDecimal bidAmount) { this.item = item; this.bidder = bidder; this.bidAmount = bidAmount; } public Bidder getBidder() { return bidder; } public BigDecimal getBidAmount() { return bidAmount; } }
额外建议
- 若系统是集群部署,本地锁(synchronized/Lock)无法跨节点生效,需引入分布式锁(如基于Redis的锁)。
- 若竞拍历史存储到数据库,要保证事务原子性,避免数据不一致。
- 可使用
ConcurrentLinkedQueue等线程安全集合存储竞拍历史,减少手动同步的工作量。
内容的提问来源于stack exchange,提问作者Lê Tuấn Anh
相关产品推荐
相关产品推荐

