synchronized 控制并发(活动秒杀)

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

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.创建一个Serviceweb

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.实现Servicespring

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

 

关于压测:apache

  压力测试是一种基本的质量保证行为,在秒杀活动中更为重要,能有效测量超卖(卖出去的比库存的多)、少卖(有买了可是没卖)等现象,目前主流的压测工具备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进行压测app

使用方法:安装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.只支持单点(单机、服务器环境),没法作到水平扩展,若是项目使用负载均衡,会出现混乱。

 

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

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

相关文章
相关标签/搜索