You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java拍卖系统竞拍并发问题求解:synchronized与Lock选型疑问

拍卖系统竞拍并发问题解决方案与代码审查

一、synchronized 还是 Lock?

  • 如果只是需要基础的互斥控制(保证同一商品的竞拍操作串行执行),synchronized完全够用。它代码简洁,JDK 1.6及以后版本对其做了偏向锁、轻量级锁等优化,性能并不比Lock类差。
  • 如果需要更灵活的锁控制,比如:
    • 公平锁(按请求顺序分配锁,避免线程饥饿)
    • 可中断的锁等待(防止线程无限阻塞)
    • 超时获取锁(避免长时间占用资源)
    • 多条件变量(区分竞拍成功/失败的等待队列)
      这种场景下ReentrantLock等Lock实现会更合适。

对于拍卖系统的竞拍场景,若无特殊复杂需求,synchronized足以应对并发问题;若追求更高的并发灵活性或需要公平竞拍机制,可以考虑使用Lock。

二、当前Bid方法的问题分析

  1. 锁粒度过大:
    方法用synchronized修饰,锁的是当前类实例(this),意味着所有商品的竞拍操作都会串行执行,严重影响系统并发能力。不同商品的竞拍是独立的,锁粒度应缩小到Item实例,而非整个服务实例。

  2. 金额精度问题:
    使用double处理金额存在精度丢失风险(比如0.1+0.2≠0.3),金融场景必须用BigDecimal保证精度。

  3. 异常处理缺失:
    unlockMoney和lockMoney操作若抛出异常,会导致数据不一致:比如已解锁上一个竞拍者的余额,但当前竞拍者余额锁定失败时,商品价格已修改、竞拍历史已添加,却无回滚机制。

  4. 线程安全依赖不明确:
    Item的getCurrentPrice/setCurrentPrice、Bidder的getBalance/lockMoney/unlockMoney方法若未做线程安全处理,即使在synchronized块中调用,也可能出现可见性或原子性问题。

  5. 失败信息模糊:
    所有失败仅输出"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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 13:27:40