7 ways to achieve high-concurrency flash kills, the writing is so good, it is recommended to collect! !

1 Introduction

High-concurrency scenarios are very common in daily work on site, especially in Internet companies. This article simulates high-concurrency scenarios by selling products in seconds. All code, scripts and test cases for the article are attached at the end of the article.

  • Environment of this article: SpringBoot 2.5.7 + MySQL 8.0 X + MybatisPlus + Swagger2.9.2
  • Simulation tool: Jmeter
  • Simulation scenario: reduce inventory -> create order -> simulate payment

2. Commodity spike - oversold

In development, you may be familiar with the following code: add @Transactionaltransaction annotations and Lock locks to the Service.

The basics of Spring Boot will not be introduced. It is recommended to watch this free tutorial:

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

Control layer: 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();
}

Business layer: 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);
}

There should be no problem with the above code, add transactions to the business method, and lock when processing the business.

But there is a problem with the above way of writing, and there will be oversold situations. Take a look at the test results: simulate 1000 concurrency, grab 100 products.

Here, the lock is added at the beginning of the business method, and the lock is released after the end of the business method. But the transaction submission here is not like this. It is possible that the lock has been released before the transaction is submitted, which will lead to oversold goods. So the timing of locking is very important!

3. Solve oversold items

For the above oversold phenomenon, the main problem occurs when the lock is released in the transaction. Before the transaction is committed, the lock has been released. (The transaction submission is executed after the entire method is executed). How to solve this problem is to advance the locking step

  • Can be locked at the controller layer
  • Aop can be used to lock before the business method is executed

3.1 Method 1 (improved version locking)

@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();
}

The above locking can solve the problem of lock release before the transaction is committed, and stress testing can be carried out in three situations:

  • Concurrent number 1000, commodity 100
  • Concurrent number 1000, commodity 1000
  • Concurrent number 2000, commodity 1000

For the case where the number of concurrency is greater than the number of products, the product seckill generally does not appear to be under-sold, but when the number of concurrency is less than or equal to the number of products, the product may be under-sold, which is also easy to understand.

If there is no problem, there will be no textures, because there are many ways, and there will be too many textures

3.2 Method 2 (AOP version lock)

For the above method of locking at the control layer, it may seem inelegant, then there is another way to lock before the transaction, that is AOP.

Recommend an open source and free Spring Boot most complete tutorial:

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

Custom AOP annotations

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

Define the aspect class

@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;
    }
}

Add AOP annotations to business methods

@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);
}

Control layer:

@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();
}

This method is more advanced and more beautiful in the use of locks!

3.3 Method 3 (pessimistic lock 1)

In addition to locking at the business code level above, you can also use the locks that come with the database for concurrency control.

Pessimistic lock, what is pessimistic lock? In layman's terms, before doing anything, a lock must be confirmed. This database-level locking operation is less efficient.

When using for update, you must add a transaction. After the transaction is processed, for update will release the row-level lock

If the number of requests is the same as the number of flash products, there will be less sales

@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();
}

Business logic

@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 layer

@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);

}

The above is to use for update to lock the query data, adding row locks

3.4 Method 4 (pessimistic lock 2)

The second way of pessimistic locking is to use the update update command to add table locks

/**
 * 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 layer

@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 Method 5 (optimistic lock)

Optimistic lock, as the name suggests, is very optimistic about the operation results, by using the version field to determine whether the data has been modified

Optimistic lock, do not check the inventory quantity, directly do inventory deduction

The optimistic lock used here will cause a large number of data update exceptions (throwing an exception will cause the purchase to fail). If the number of snap-ups configured is relatively small, such as 120:100 (number of people: commodity), there will be fewer purchases. Optimism is not recommended Lock.

@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);
}

Optimistic locks will cause a large number of data update exceptions (throwing an exception will cause the purchase to fail), and there will be less purchases. Optimistic locks are not recommended

3.6 Method 6 (blocking queue)

Using blocking queues can also solve high concurrency problems. The idea is to store the received requests in the queue in order, and the consumer thread fetches data from the queue one by one for processing, see the specific code.

Blocking queue: Here, the static inner class is used to implement the singleton mode, and there will be no problem under concurrent conditions.

// 秒杀队列(固定长度为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();
    }
}

Consumption seckill queue: implement the ApplicationRunner interface

// 消费秒杀队列
@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();
}

Note: In the business layer and AOP methods, no exceptions can be thrown, and the exception throwing codes such as throw new RuntimeException() should be commented out. Because once the program throws an exception, it will stop, resulting in the termination of the consumption seckill queue process!

There are a few points to note when using blocking queues to implement spikes:

  • It is also easy to understand that calling business methods in the consumption seckill queue is locked and not locked, that is seckillService.startSecondKillByAop(), seckillService.startSecondKillByLock()the result of the method is the same.
  • When the queue length is consistent with the quantity of goods, there will be less sales, and the value can be increased
  • The following is the undersell when the queue length is 1000, the number of products is 1000, and the number of concurrency is 2000.

3.7. Method 7 (Disruptor queue)

Disruptor is a high-performance queue. The original intention of research and development is to solve the delay problem of memory queue. In the performance test, it is found that it is in the same order of magnitude as I/O operations. The single thread of the system developed based on Disruptor can support 6 million orders per second.

// 事件生成工厂(用来初始化预分配事件对象)
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();
}

After testing, it is found that using the Disruptor queue has the same problem as the custom queue, and there will also be oversold situations, but the efficiency has been improved.

4. Summary

For the above seven ways to achieve concurrency, make a summary:

  • The first and second methods are to use locks and transactions in the code to solve the concurrency problem. The main solution is that the lock needs to be loaded before the transaction
  • Methods 3, 4, and 5 mainly use database locks to solve concurrency problems. Method 3 uses for upate to add row locks to the table. Method 4 uses update to lock tables. Method 5 controls the database by adding the version field. Update operation, method 5 has the worst effect
  • Ways 6 and 7 use queues to solve the concurrency problem. What needs special attention here is that exceptions cannot be thrown through throw in the code, otherwise the consumption thread will be terminated, and because there is a time gap between entering and exiting the queue, fewer products will be sold

All the above situations have been tested by code, and the tests are divided into three situations:

  • The number of concurrency is 1000, and the number of products is 100
  • The number of concurrency is 1000, and the number of products is 1000
  • The number of concurrency is 2000, and the number of products is 1000

Thinking: How to solve the concurrency problem in a distributed situation? Continue to experiment next time.

Copyright statement: This article is an original article of CSDN blogger "Stop Going Forward", following the CC 4.0 BY-SA copyright agreement, please attach the original source link and this statement for reprinting. Original link: https://blog.csdn.net/zxd1435513775/article/details/122643285

Recent hot article recommendation:

1. 1,000+ Java interview questions and answers (2022 latest version)

2. Brilliant! Java coroutines are coming. . .

3. Spring Boot 2.x tutorial, too comprehensive!

4. Don't fill the screen with explosions and explosions, try the decorator mode, this is the elegant way! !

5. The latest release of "Java Development Manual (Songshan Edition)", download quickly!

Feel good, don't forget to like + forward!

Guess you like

Origin blog.csdn.net/youanyyou/article/details/130626223