PostMan不是严格意义上的并发请求工具,实际是串行的,如果需要测试后台接口并发时程序的准确性,建议采用JMeter工具。
案例:JMeter设置20个并发卖票请求,查看后台是否存在超卖的情况
方式一:一共10张票,票余额作为成员属性放在控制器中,没做任何的锁控制
package com.gingko.controller;
import com.gingko.common.GenericWebResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
@RestController
@RequestMapping("ticket")
@Slf4j
public class TicketController {
private static int TICKET_AMOUNT = 10;//一共10张票
@PostMapping("/sell")
public GenericWebResult sell() {
GenericWebResult result = null;
if(TICKET_AMOUNT > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
TICKET_AMOUNT--;
System.out.println("售票成功:余票" + TICKET_AMOUNT);
result = GenericWebResult.ok("售票成功:余票",TICKET_AMOUNT);
}else {
result = GenericWebResult.error("售票失败:余票",TICKET_AMOUNT);
System.out.println("售票失败:余票" + TICKET_AMOUNT);
}
return result;
}
}
运行结果及分析:
从运行结果可以看出,在高并发的时候,出现了超卖和数据不一致的问题
方式二:一共10张票,票余额作为成员属性放在控制器中,卖票的请求方法上加锁synchronized
package com.gingko.controller;
import com.gingko.common.GenericWebResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
@RestController
@RequestMapping("ticket")
@Slf4j
public class TicketController {
private static int TICKET_AMOUNT = 10;//一共10张票
@PostMapping("/sell")
public synchronized GenericWebResult sell() {
GenericWebResult result = null;
if(TICKET_AMOUNT > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
TICKET_AMOUNT--;
System.out.println("售票成功:余票" + TICKET_AMOUNT);
result = GenericWebResult.ok("售票成功:余票",TICKET_AMOUNT);
}else {
result = GenericWebResult.error("售票失败:余票",TICKET_AMOUNT);
System.out.println("售票失败:余票" + TICKET_AMOUNT);
}
return result;
}
}
运行结果及分析:
从运行结果可以看出,在高并发的时候,没有出现超卖或数据不一致问题,数据结果符合预期。
方式三:TicketController对象多例,并且TICKET_AMOUNT属性不是static,每个对象各自持有
package com.gingko.controller;
import com.gingko.common.GenericWebResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Scope;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
@RestController
@RequestMapping("ticket")
@Slf4j
@Scope("prototype") //多例
public class TicketController {
private int TICKET_AMOUNT = 10;
@PostMapping("/sell")
public GenericWebResult sell() {
GenericWebResult result = null;
if(TICKET_AMOUNT > 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
TICKET_AMOUNT--;
System.out.println("售票成功:余票" + TICKET_AMOUNT);
result = GenericWebResult.ok("售票成功:余票",TICKET_AMOUNT);
}else {
result = GenericWebResult.error("售票失败:余票",TICKET_AMOUNT);
System.out.println("售票失败:余票" + TICKET_AMOUNT);
}
return result;
}
}
运行结果及分析:
从程序可以看出,20个并发请求,后台生成了20个TicketController实例,每个实例内部都持有属性TICKET_AMOUNT = 10,sell方法执行后,TICKET_AMOUNT = 9
方式一、方式二与方式三示意图区别如下:
方式一、二,堆内存中只有一个TicketController实例,并且TICKET_AMOUNT不属于实例对象,属于共享的数据区,对这种共享数据并发操作时,需要锁机制控制,防止出现数据不一致。
方式三,并发请求多少,堆内存中生成多少个TicketController实例,并且每个TicketController实例各自持有TICKET_AMOUNT,访问各自的成员属性也就不会发生数据不一致问题。