关于java:实现高并发秒杀的-7-种方式写的太好了建议收藏

1.引言

高并发场景在现场的日常工作中很常见,特地是在互联网公司中,这篇文章就来通过秒杀商品来模仿高并发的场景。文章开端会附上文章的所有代码、脚本和测试用例。

  • 本文环境: SpringBoot 2.5.7 + MySQL 8.0 X + MybatisPlus + Swagger2.9.2
  • 模仿工具: Jmeter
  • 模仿场景: 减库存->创立订单->模仿领取

2.商品秒杀-超卖

在开发中,对于上面的代码,可能很相熟:在Service外面加上@Transactional事务注解和Lock锁。

Spring Boot 根底就不介绍了,举荐看这个收费教程:

https://github.com/javastacks/spring-boot-best-practice

管制层:Controller

@ApiOperation(value="秒杀实现形式——Lock加锁")
@PostMapping("/start/lock")
public Result startLock(long skgId){
    try {
        log.info("开始秒杀形式一...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByLock(skgId, userId);
        if(result != null){
            log.info("用户:{}--{}", userId, result.get("msg"));
        }else{
            log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {

    }
    return Result.ok();
}

业务层:Service

@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByLock(long skgId, long userId) {
    lock.lock();
    try {
        // 校验库存
        SecondKill secondKill = secondKillMapper.selectById(skgId);
        Integer number = secondKill.getNumber();
        if (number > 0) {
            // 扣库存
            secondKill.setNumber(number - 1);
            secondKillMapper.updateById(secondKill);
            // 创立订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            // 模仿领取
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {
            return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {
        throw new ScorpiosException("异样了个乖乖");
    } finally {
        lock.unlock();
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

对于下面的代码应该没啥问题吧,业务办法上加事务,在解决业务的时候加锁。

但下面这样写法是有问题的,会呈现超卖的状况,看下测试后果:模仿1000个并发,抢100商品。

这里在业务办法开始加了锁,在业务办法完结后开释了锁。但这里的事务提交却不是这样的,有可能在事务提交之前,就曾经把锁开释了,这样会导致商品超卖景象。所以加锁的机会很重要!

3. 解决商品超卖

对于下面超卖景象,次要问题呈现在事务中锁开释的机会,事务未提交之前,锁曾经开释。(事务提交是在整个办法执行完)。如何解决这个问题呢,就是把加锁步骤提前

  • 能够在controller层进行加锁
  • 能够应用Aop在业务办法执行之前进行加锁

3.1 形式一(改进版加锁)

@ApiOperation(value="秒杀实现形式——Lock加锁")
@PostMapping("/start/lock")
public Result startLock(long skgId){
    // 在此处加锁
    lock.lock();
    try {
        log.info("开始秒杀形式一...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByLock(skgId, userId);
        if(result != null){
            log.info("用户:{}--{}", userId, result.get("msg"));
        }else{
            log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 在此处开释锁
        lock.unlock();
    }
    return Result.ok();
}

下面这样的加锁就能够解决事务未提交之前,锁开释的问题,能够分三种状况进行压力测试:

  • 并发数1000,商品100
  • 并发数1000,商品1000
  • 并发数2000,商品1000

对于并发量大于商品数的状况,商品秒杀个别不会呈现少卖的请况,但对于并发数小于等于商品数的时候可能会呈现商品少卖状况,这也很好了解。

对于没有问题的状况就不贴图了,因为有很多种形式,贴图会太多

3.2 形式二(AOP版加锁)

对于下面在管制层进行加锁的形式,可能显得不优雅,那就还有另一种形式进行在事务之前加锁,那就是AOP。

举荐一个开源收费的 Spring Boot 最全教程:

https://github.com/javastacks/spring-boot-best-practice

自定义AOP注解

@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public  @interface ServiceLock {
    String description()  default "";
}

定义切面类

@Slf4j
@Component
@Scope
@Aspect
@Order(1) //order越小越是最先执行,但更重要的是最先执行的最初完结
public class LockAspect {
    /**
     * 思考:为什么不必synchronized
     * service 默认是单例的,并发下lock只有一个实例
     */
    private static  Lock lock = new ReentrantLock(true); // 互斥锁 参数默认false,不偏心锁

    // Service层切点     用于记录谬误日志
    @Pointcut("@annotation(com.scorpios.secondkill.aop.ServiceLock)")
    public void lockAspect() {

    }

    @Around("lockAspect()")
    public  Object around(ProceedingJoinPoint joinPoint) {
        lock.lock();
        Object obj = null;
        try {
            obj = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
   throw new RuntimeException();
        } finally{
            lock.unlock();
        }
        return obj;
    }
}

在业务办法上增加AOP注解

@Override
@ServiceLock // 应用Aop进行加锁
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByAop(long skgId, long userId) {

    try {
        // 校验库存
        SecondKill secondKill = secondKillMapper.selectById(skgId);
        Integer number = secondKill.getNumber();
        if (number > 0) {
            //扣库存
            secondKill.setNumber(number - 1);
            secondKillMapper.updateById(secondKill);
            //创立订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            //领取
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {
            return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {
        throw new ScorpiosException("异样了个乖乖");
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

管制层:

@ApiOperation(value="秒杀实现形式二——Aop加锁")
@PostMapping("/start/aop")
public Result startAop(long skgId){
    try {
        log.info("开始秒杀形式二...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByAop(skgId, userId);
        if(result != null){
            log.info("用户:{}--{}", userId, result.get("msg"));
        }else{
            log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return Result.ok();
}

这种形式在对锁的应用上,更高阶、更好看!

3.3 形式三(乐观锁一)

除了下面在业务代码层面加锁外,还能够应用数据库自带的锁进行并发管制。

乐观锁,什么是乐观锁呢?艰深的说,在做任何事件之前,都要进行加锁确认。这种数据库级加锁操作效率较低。

应用for update肯定要加上事务,当事务处理完后,for update才会将行级锁解除

如果申请数和秒杀商品数量统一,会呈现少卖

@ApiOperation(value="秒杀实现形式三——乐观锁")
@PostMapping("/start/pes/lock/one")
public Result startPesLockOne(long skgId){
    try {
        log.info("开始秒杀形式三...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        Result result = secondKillService.startSecondKillByUpdate(skgId, userId);
        if(result != null){
            log.info("用户:{}--{}", userId, result.get("msg"));
        }else{
            log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return Result.ok();
}

业务逻辑

@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByUpdate(long skgId, long userId) {
    try {
        // 校验库存-乐观锁
        SecondKill secondKill = secondKillMapper.querySecondKillForUpdate(skgId);
        Integer number = secondKill.getNumber();
        if (number > 0) {
            //扣库存
            secondKill.setNumber(number - 1);
            secondKillMapper.updateById(secondKill);
            //创立订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            //领取
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {
            return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {
        throw new ScorpiosException("异样了个乖乖");
    } finally {
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

Dao层

@Repository
public interface SecondKillMapper extends BaseMapper<SecondKill> {

    /**
     * 将此行数据进行加锁,当整个办法将事务提交后,才会解锁
     * @param skgId
     * @return
     */
    @Select(value = "SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
    SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);

}

下面是利用for update进行对查问数据加锁,加的是行锁

3.4 形式四(乐观锁二)

乐观锁的第二种形式就是利用update更新命令来加表锁

/**
 * UPDATE锁表
 * @param skgId  商品id
 * @param userId    用户id
 * @return
 */
@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByUpdateTwo(long skgId, long userId) {
    try {

        // 不校验,间接扣库存更新
        int result = secondKillMapper.updateSecondKillById(skgId);
        if (result > 0) {
            //创立订单
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(skgId);
            killed.setUserId(userId);
            killed.setState((short) 0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            successKilledMapper.insert(killed);

            //领取
            Payment payment = new Payment();
            payment.setSeckillId(skgId);
            payment.setSeckillId(skgId);
            payment.setUserId(userId);
            payment.setMoney(40);
            payment.setState((short) 1);
            payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
            paymentMapper.insert(payment);
        } else {
            return Result.error(SecondKillStateEnum.END);
        }
    } catch (Exception e) {
        throw new ScorpiosException("异样了个乖乖");
    } finally {
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}

Dao层

@Repository
public interface SecondKillMapper extends BaseMapper<SecondKill> {

    /**
     * 将此行数据进行加锁,当整个办法将事务提交后,才会解锁
     * @param skgId
     * @return
     */
    @Select(value = "SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
    SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);

    @Update(value = "UPDATE seckill SET number=number-1 WHERE seckill_id=#{skgId} AND number > 0")
    int updateSecondKillById(@Param("skgId") long skgId);
}

3.5 形式五(乐观锁)

乐观锁,顾名思义,就是对操作后果很乐观,通过利用version字段来判断数据是否被批改

乐观锁,不进行库存数量的校验,间接做库存扣减

这里应用的乐观锁会呈现大量的数据更新异样(抛异样就会导致购买失败)、如果配置的抢购人数比拟少、比方120:100(人数:商品) 会呈现少买的状况,不举荐应用乐观锁。

@ApiOperation(value="秒杀实现形式五——乐观锁")
@PostMapping("/start/opt/lock")
public Result startOptLock(long skgId){
    try {
        log.info("开始秒杀形式五...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        // 参数增加了购买数量
        Result result = secondKillService.startSecondKillByPesLock(skgId, userId,1);
        if(result != null){
            log.info("用户:{}--{}", userId, result.get("msg"));
        }else{
            log.info("用户:{}--{}", userId, "哎呦喂,人也太多了,请稍后!");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return Result.ok();
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result startSecondKillByPesLock(long skgId, long userId, int number) {

    // 乐观锁,不进行库存数量的校验,间接
    try {
        SecondKill kill = secondKillMapper.selectById(skgId);
        // 残余的数量应该要大于等于秒杀的数量
        if(kill.getNumber() >= number) {
            int result = secondKillMapper.updateSecondKillByVersion(number,skgId,kill.getVersion());
            if (result > 0) {
                //创立订单
                SuccessKilled killed = new SuccessKilled();
                killed.setSeckillId(skgId);
                killed.setUserId(userId);
                killed.setState((short) 0);
                killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
                successKilledMapper.insert(killed);

                //领取
                Payment payment = new Payment();
                payment.setSeckillId(skgId);
                payment.setSeckillId(skgId);
                payment.setUserId(userId);
                payment.setMoney(40);
                payment.setState((short) 1);
                payment.setCreateTime(new Timestamp(System.currentTimeMillis()));
                paymentMapper.insert(payment);
            } else {
                return Result.error(SecondKillStateEnum.END);
            }
        }
    } catch (Exception e) {
        throw new ScorpiosException("异样了个乖乖");
    } finally {
    }
    return Result.ok(SecondKillStateEnum.SUCCESS);
}
@Repository
public interface SecondKillMapper extends BaseMapper<SecondKill> {

    /**
     * 将此行数据进行加锁,当整个办法将事务提交后,才会解锁
     * @param skgId
     * @return
     */
    @Select(value = "SELECT * FROM seckill WHERE seckill_id=#{skgId} FOR UPDATE")
    SecondKill querySecondKillForUpdate(@Param("skgId") Long skgId);

    @Update(value = "UPDATE seckill SET number=number-1 WHERE seckill_id=#{skgId} AND number > 0")
    int updateSecondKillById(@Param("skgId") long skgId);

    @Update(value = "UPDATE seckill  SET number=number-#{number},version=version+1 WHERE seckill_id=#{skgId} AND version = #{version}")
    int updateSecondKillByVersion(@Param("number") int number, @Param("skgId") long skgId, @Param("version")int version);
}

乐观锁会呈现大量的数据更新异样(抛异样就会导致购买失败),会呈现少买的状况,不举荐应用乐观锁

3.6 形式六(阻塞队列)

利用阻塞队类,也能够解决高并发问题。其思维就是把接管到的申请按程序寄存到队列中,消费者线程逐个从队列里取数据进行解决,看下具体代码。

阻塞队列:这里应用动态外部类的形式来实现单例模式,在并发条件下不会呈现问题。

// 秒杀队列(固定长度为100)
public class SecondKillQueue {

    // 队列大小
    static final int QUEUE_MAX_SIZE = 100;

    // 用于多线程间下单的队列
    static BlockingQueue<SuccessKilled> blockingQueue = new LinkedBlockingQueue<SuccessKilled>(QUEUE_MAX_SIZE);

    // 应用动态外部类,实现单例模式
    private SecondKillQueue(){};

    private static class SingletonHolder{
        // 动态初始化器,由JVM来保障线程平安
        private  static SecondKillQueue queue = new SecondKillQueue();
    }

    /**
     * 单例队列
     * @return
     */
    public static SecondKillQueue getSkillQueue(){
        return SingletonHolder.queue;
    }

    /**
     * 生产入队
     * @param kill
     * @throws InterruptedException
     * add(e) 队列未满时,返回true;队列满则抛出IllegalStateException(“Queue full”)异样——AbstractQueue
     * put(e) 队列未满时,直接插入没有返回值;队列满时会阻塞期待,始终等到队列未满时再插入。
     * offer(e) 队列未满时,返回true;队列满时返回false。非阻塞立刻返回。
     * offer(e, time, unit) 设定期待的工夫,如果在指定工夫内还不能往队列中插入数据则返回false,插入胜利返回true。
     */
    public  Boolean  produce(SuccessKilled kill) {
        return blockingQueue.offer(kill);
    }
    /**
     * 生产出队
     * poll() 获取并移除队首元素,在指定的工夫内去轮询队列看有没有首元素有则返回,否者超时后返回null
     * take() 与带超时工夫的poll相似不同在于take时候如果以后队列空了它会始终期待其余线程调用notEmpty.signal()才会被唤醒
     */
    public  SuccessKilled consume() throws InterruptedException {
        return blockingQueue.take();
    }

    /**
     * 获取队列大小
     * @return
     */
    public int size() {
        return blockingQueue.size();
    }
}

生产秒杀队列:实现ApplicationRunner接口

// 生产秒杀队列
@Slf4j
@Component
public class TaskRunner implements ApplicationRunner{

    @Autowired
    private SecondKillService seckillService;

    @Override
    public void run(ApplicationArguments var){
        new Thread(() -> {
            log.info("队列启动胜利");
            while(true){
                try {
                    // 过程内队列
                    SuccessKilled kill = SecondKillQueue.getSkillQueue().consume();
                    if(kill != null){
                        Result result = seckillService.startSecondKillByAop(kill.getSeckillId(), kill.getUserId());
                        if(result != null && result.equals(Result.ok(SecondKillStateEnum.SUCCESS))){
                            log.info("TaskRunner,result:{}",result);
                            log.info("TaskRunner从音讯队列取出用户,用户:{}{}",kill.getUserId(),"秒杀胜利");
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
@ApiOperation(value="秒杀实现形式六——音讯队列")
@PostMapping("/start/queue")
public Result startQueue(long skgId){
    try {
        log.info("开始秒杀形式六...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        SuccessKilled kill = new SuccessKilled();
        kill.setSeckillId(skgId);
        kill.setUserId(userId);
        Boolean flag = SecondKillQueue.getSkillQueue().produce(kill);
        // 尽管进入了队列,然而不肯定能秒杀胜利 进队出队有工夫间隙
        if(flag){
            log.info("用户:{}{}",kill.getUserId(),"秒杀胜利");
        }else{
            log.info("用户:{}{}",userId,"秒杀失败");
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    return Result.ok();
}

留神:在业务层和AOP办法中,不能抛出任何异样, throw new RuntimeException()这些抛异样代码要正文掉。因为一旦程序抛出异样就会进行,导致生产秒杀队列过程终止!

应用阻塞队列来实现秒杀,有几点要留神:

  • 生产秒杀队列中调用业务办法加锁与不加锁状况一样,也就是seckillService.startSecondKillByAop()seckillService.startSecondKillByLock()办法后果一样,这也很好了解
  • 当队列长度与商品数量统一时,会呈现少卖的景象,能够调大数值
  • 上面是队列长度1000,商品数量1000,并发数2000状况下呈现的少卖

3.7.形式七(Disruptor队列)

Disruptor是个高性能队列,研发的初衷是解决内存队列的提早问题,在性能测试中发现居然与I/O操作处于同样的数量级,基于Disruptor开发的零碎单线程能撑持每秒600万订单。

// 事件生成工厂(用来初始化预调配事件对象)
public class SecondKillEventFactory implements EventFactory<SecondKillEvent> {

    @Override
    public SecondKillEvent newInstance() {
        return new SecondKillEvent();
    }
}
// 事件对象(秒杀事件)
public class SecondKillEvent implements Serializable {
    private static final long serialVersionUID = 1L;
    private long seckillId;
    private long userId;

 // set/get办法略

}
// 应用translator形式生产者
public class SecondKillEventProducer {

    private final static EventTranslatorVararg<SecondKillEvent> translator = (seckillEvent, seq, objs) -> {
        seckillEvent.setSeckillId((Long) objs[0]);
        seckillEvent.setUserId((Long) objs[1]);
    };

    private final RingBuffer<SecondKillEvent> ringBuffer;

    public SecondKillEventProducer(RingBuffer<SecondKillEvent> ringBuffer){
        this.ringBuffer = ringBuffer;
    }

    public void secondKill(long seckillId, long userId){
        this.ringBuffer.publishEvent(translator, seckillId, userId);
    }
}
// 消费者(秒杀处理器)
@Slf4j
public class SecondKillEventConsumer implements EventHandler<SecondKillEvent> {

    private SecondKillService secondKillService = (SecondKillService) SpringUtil.getBean("secondKillService");

    @Override
    public void onEvent(SecondKillEvent seckillEvent, long seq, boolean bool) {
        Result result = secondKillService.startSecondKillByAop(seckillEvent.getSeckillId(), seckillEvent.getUserId());
        if(result.equals(Result.ok(SecondKillStateEnum.SUCCESS))){
            log.info("用户:{}{}",seckillEvent.getUserId(),"秒杀胜利");
        }
    }
}
public class DisruptorUtil {

    static Disruptor<SecondKillEvent> disruptor;

    static{
        SecondKillEventFactory factory = new SecondKillEventFactory();
        int ringBufferSize = 1024;
        ThreadFactory threadFactory = runnable -> new Thread(runnable);
        disruptor = new Disruptor<>(factory, ringBufferSize, threadFactory);
        disruptor.handleEventsWith(new SecondKillEventConsumer());
        disruptor.start();
    }

    public static void producer(SecondKillEvent kill){
        RingBuffer<SecondKillEvent> ringBuffer = disruptor.getRingBuffer();
        SecondKillEventProducer producer = new SecondKillEventProducer(ringBuffer);
        producer.secondKill(kill.getSeckillId(),kill.getUserId());
    }
}
@ApiOperation(value="秒杀实现形式七——Disruptor队列")
@PostMapping("/start/disruptor")
public Result startDisruptor(long skgId){
    try {
        log.info("开始秒杀形式七...");
        final long userId = (int) (new Random().nextDouble() * (99999 - 10000 + 1)) + 10000;
        SecondKillEvent kill = new SecondKillEvent();
        kill.setSeckillId(skgId);
        kill.setUserId(userId);
        DisruptorUtil.producer(kill);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return Result.ok();
}

通过测试,发现应用Disruptor队列队列,与自定义队列有着同样的问题,也会呈现超卖的状况,但效率有所提高。

4. 小结

对于下面七种实现并发的形式,做一下总结:

  • 一、二形式是在代码中利用锁和事务的形式解决了并发问题,次要解决的是锁要加载事务之前
  • 三、四、五形式次要是数据库的锁来解决并发问题,形式三是利用for upate对表加行锁,形式四是利用update来对表加锁,形式五是通过减少version字段来管制数据库的更新操作,形式五的成果最差
  • 六、七形式是通过队列来解决并发问题,这里须要特地留神的是,在代码中不能通过throw抛异样,否则生产线程会终止,而且因为进队和出队存在工夫间隙,会导致商品少卖

下面所有的状况都通过代码测试,测试分一下三种状况:

  • 并发数1000,商品数100
  • 并发数1000,商品数1000
  • 并发数2000,商品数1000

思考:分布式状况下如何解决并发问题呢?下次持续试验。

版权申明:本文为CSDN博主「止步前行」的原创文章,遵循CC 4.0 BY-SA版权协定,转载请附上原文出处链接及本申明。原文链接:https://blog.csdn.net/zxd1435513775/article/details/122643285

近期热文举荐:

1.1,000+ 道 Java面试题及答案整顿(2022最新版)

2.劲爆!Java 协程要来了。。。

3.Spring Boot 2.x 教程,太全了!

4.别再写满屏的爆爆爆炸类了,试试装璜器模式,这才是优雅的形式!!

5.《Java开发手册(嵩山版)》最新公布,速速下载!

感觉不错,别忘了顺手点赞+转发哦!

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理