• synchronized 控制并发(活动秒杀)


    1.首先我们新建一个Controller用于秒杀:

    package com.imooc.Controller;
    
    import com.imooc.service.impl.SeckillServiceImpl;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    /**
     * Created by zhongliahi on 2018/6/11.
     * 秒杀测试
     */
    @RestController
    @RequestMapping(value = "/skill")
    @Slf4j
    public class SeckillController {
    
        @Autowired
        private SeckillServiceImpl seckillService;
    
    
        //@PathVariable 可以将 URL 中占位符参数绑定到控制器处理方法的入参中
        // URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到操作方法的入参中。
        @GetMapping(value = "/query/{productId}")
        public String query(@PathVariable String productId) throws Exception{
            return seckillService.querySeckillProductInfo(productId);
        }
    
    
        @GetMapping("/order/{productId}")
        public String skill(@PathVariable String productId) throws Exception{
            log.info("秒杀----productId:"+productId);
            seckillService.orderProductMockDiffUser(productId);
    
            return seckillService.querySeckillProductInfo(productId);
        }
    }
    

      

     2.建立一个Service

    package com.imooc.service;
    
    /**
     * Created by zhongliahi on 2018/6/11.
     */
    public interface SeckillService {
    
        String queryMap(String productId);
    
    
        String querySeckillProductInfo(String productId);
    
    
        void orderProductMockDiffUser(String productId);
    }

     3.实现Service

    package com.imooc.service.impl;
    
    import com.imooc.Exception.SellException;
    import com.imooc.enums.ExceptionEnum;
    import com.imooc.service.SeckillService;
    import com.imooc.util.KeyUtils;
    import org.springframework.stereotype.Service;
    
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * Created by zhonglihai on 2018/6/11.
     * 秒杀Serviceimpl
     * 演示
     */
    @Service
    public class SeckillServiceImpl implements SeckillService {
    
        /**
         * 秒杀特价 1000000份
         * @param productId
         * @return
         */
        static Map<String,Integer> products;
        static Map<String,Integer> stock;
        static Map<String,String> orders;
    
        static{
            /**
             * ,模拟多个表,商品信息表,库存表,秒杀成功订单表
             */
            products =new HashMap<>();
            stock=new HashMap<>();
            orders=new HashMap<>();
            products.put("123",1000000);
            stock.put("123",1000000);
        }
    
        @Override
        public String queryMap(String productId) {
            return "活动特价,限量:"+products.get(productId)+",还剩:"+stock.get(productId)
                    +"份"+",成功下单用户数:"+orders.size()+"人。";
        }
    
        @Override
        public String querySeckillProductInfo(String productId) {
            return this.queryMap(productId);
        }
    
        /**
         * 主要秒杀的逻辑
         * @param productId
         */
        @Override
        public synchronized void   orderProductMockDiffUser(String productId) {
            //查询该商品库存,为0则活动结束
            int stockNum=stock.get(productId);
            if(stockNum==0){
                throw new SellException(ExceptionEnum.SECKILL_OVER);
            }else{
                //2.下单(模拟不同用户opendid不同)
                orders.put(KeyUtils.getUniqueKey(),productId);
    
                //3.减库存
                stockNum=stockNum-1;
                try{
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                stock.put(productId,stockNum);
            }
        }
    }
    

    关于压测:

      压力测试是一种基本的质量保证行为,在秒杀活动中更为重要,能有效测量超卖(卖出去的比库存的多)、少卖(有买了但是没卖)等现象,目前主流的压测工具有Jmeter、LoadRunner等,老一点的有apache ab,正好本人机器装有Apace服务,因此使用apache  ab做压测。

    项目中,我们只添加了一件商品,productId为123,启动项目,在浏览器中查询,URL:http://127.0.0.1:8080/sell/skill/query/123,结果如下:

    可以看到,项目能正常访问,查询的库存为1000000份。现在我们咋浏览器上进行秒杀,URL:http://127.0.0.1:8080/sell/skill/order/123

    我们已经秒杀了一件商品,那么如何实现高并发秒杀呢?这就需要使用上面介绍的Apache ab进行压测

    使用方法:安装Apache Http Services 后,配置相关环境变量,保证能在命令行直接调用。

    测试命令:ab -n 1000 -c 10 http://127.0.0.1:8080/sell/skill/order/123

      其中ab表示在命令行调取apache ab压测工具,-n表示发起1000条请求,-c 表示10个并发

      http://127.0.0.1:8080/sell/skill/order/123 :表示测试的URL.

      (注意:压测会占用大量电脑资源,特别是并发大的时候)

    结果:

    可以发现,秒杀还是比较快的,仅用了19秒。现在我们来查看库存;

    -----------------------------------------

    重点:查看库存发现虽然秒杀都成功了,但是库存量与下单成功量之和与总量不对应:999878+1000>1000000,出现了超卖现象。

    下面我们在秒杀方法上加上synchronized关键字,修SeckillServiceImpl,对秒杀方法上锁

    然后重启项目,在此重新查询库存:

     

    没问题!

    继续并发秒杀:

    测试完成,明显可以感觉到,访问慢了很多,用了100多秒,因为我们使用了资源锁,保证每次只有一个线程去调用它。

    现在我们在来查看库存。

    重点:可以发现,库存与下单都是正确的,使用synchronized是一种资源控制的解决办法

    那么,秒杀中直接使用synchronized进行锁控制有什么不好的地方呢?

      1.无法做到细粒度的控制,在测试中,我们只有一个商品,如果有多个商品呢?

      多个商品参与秒杀活动,有的人秒杀商品A、有的秒杀商品B,都要走秒杀方法,使用synchronized

      会一样的慢。

      2.只支持单点(单机、服务器环境),无法做到水平扩展,如果项目使用负载均衡,会出现混乱。

    那么,又有什么好的办法可以解决上面提到的问题?

    答案当然是有,那就是分布式锁。

      

      

  • 相关阅读:
    HTML 布局
    HTML <div> 和<span>
    HTML 列表
    HTML 表格
    可视化反投射:坍塌尺寸的概率恢复:ICCV9论文解读
    智能座舱虚拟机系统
    深度学习白平衡(Color Constancy,AWB):ICCV2019论文解析
    面部表情视频中进行远程心率测量:ICCV2019论文解析
    高精地图中导航标识识别
    人脸标记检测:ICCV2019论文解析
  • 原文地址:https://www.cnblogs.com/zhonglihai/p/9167120.html
Copyright © 2020-2023  润新知