解决优惠卷一人一票的问题(只能解决单机环境下的问题,集群无法解决)

47 阅读2分钟

完整代码

package com.hmdp.service.impl;

import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.RedisIdWorker;
import com.hmdp.utils.UserHolder;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 
 * @since 2021-12-22
 */
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {

    @Resource
    private ISeckillVoucherService seckillVoucherService;
    @Resource
    private RedisIdWorker redisIdWorker;
    @Override

    public Result seckillVoucher(Long voucherId) {
        // 1 查询优惠卷信息
        SeckillVoucher seckillVoucher = seckillVoucherService.getById(voucherId);
        // 2 判断秒杀是否开始
        if (seckillVoucher.getBeginTime().isAfter(LocalDateTime.now())) {
            // 尚未开始
            return Result.fail("秒杀尚未开始");
        }
        // 3 判断秒杀是否结束
        if (seckillVoucher.getEndTime().isBefore(LocalDateTime.now())){
            // 已经结束
            return Result.fail("秒杀已经结束");
        }
        // 4 判断库存是否充足
        if (seckillVoucher.getStock() < 1) {
            // 库存不足
            return Result.fail("库存不足");
        }

        Long userId = UserHolder.getUser().getId();
        // 5 创建订单
        // 5.2 加锁
        synchronized (userId.toString().intern()){
            // 获取代理对象(事务)
            IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
            return proxy.createVoucherOrder(voucherId);
        }
    }
    @Transactional
    public Result createVoucherOrder(Long voucherId) {
        // 5.1 判断用户是否已经购买过
        Long userId = UserHolder.getUser().getId();
        // 5.3 查询订单
        int count = query().eq("user_id", userId).eq("voucher_id", voucherId).count();
        if (count > 0) {
            // 用户已经购买过
            return Result.fail("用户已经购买过");
        }
        // 5.4 扣减库存
        boolean success = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .gt("stock",0)
                .update();
        if (!success){
            return Result.fail("库存不足");
        }
        // 6 创建订单
        VoucherOrder voucherOrder = new VoucherOrder();
        // 6.1 订单id
        long orderId = redisIdWorker.nextId("order");
        voucherOrder.setId(orderId);
        // 6.2 用户id
        voucherOrder.setUserId(userId);
        // 6.3 优惠券id
        voucherOrder.setVoucherId(voucherId);
        save(voucherOrder);
        // 7 返回订单id
        return Result.ok(orderId);
    }
}

关键代码

synchronized (userId.toString().intern()){
    // 获取代理对象(事务)
    IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
    return proxy.createVoucherOrder(voucherId);
}

对单个用户加锁

将创建订单的部分提取到createVoucherOrder()方法中,通过 synchronized 块对用户 ID 加锁,使用 userId.toString().intern() 作为锁对象,确保相同用户 ID 的字符串对象在 JVM 字符串池中唯一,intern() 方法保证不同线程中相同用户 ID 的字符串引用指向同一内存地址,实现精确锁定。保证同一用户只能创建一个订单。

使用代理对象确保事务正常

自调用问题: 如果直接在 synchronized 块中调用 this.createVoucherOrder(),由于 this 指向的是原始对象而非代理对象,​事务注解 @Transactional 将失效​(Spring 的事务基于代理对象实现) ​AopContext.currentProxy()的解决方案: 获取当前代理对象 AopContext.currentProxy() 返回当前执行代码的代理对象,确保后续方法调用通过代理执行

 ​启用代理暴露

  • 启动类配置
    需在 Spring Boot 启动类添加 @EnableAspectJAutoProxy(exposeProxy = true),允许通过 AopContext 暴露代理对象
    java
    @SpringBootApplication
    @EnableAspectJAutoProxy(exposeProxy = true)
    public class Application { ... }
    

总结

代码通过 synchronized 解决了单机环境下的线程安全问题,但事务完整性仍需依赖锁与事务的边界对齐。若需高并发场景下的严格安全,建议采用分布式锁或调整事务边界。对于生产环境,优先选择分布式锁(如 Redisson)并保持默认隔离级别,以平衡性能与安全性