高并发操作同一个数据造成错误逻辑数据问题
高并发操作同一个数据会产生数据超库的问题,也就是本来商品剩余量最小应该是0,但是由于高并发可能引起剩余量成为负数,订购表中关系也会增加很多,最终就是商品没了,但是客户全部显示订购成功。
最开始商品数据是10,订购表为空。
但是若按照寻常的代码逻辑走,如下:
这个是dao层代码,更新商品库存数量的方法。
public int reduceNum1(int num,String id){
String sql = "update SysKc t set t.num=(t.num-?) where t.id=? ";
//Integer i = this.execute(sql,num,id);
return this.execute(sql,num,id);
}
这个是controller层调用的方法。
public String SecKill() throws InterruptedException {
try {
// String consumer = request.getParameter("consumer");
String goodId = request.getParameter("goodsId");
int nums = Integer.parseInt(request.getParameter("num"));
for(int i=0;i<50;i++){
final String consumer="consumer"+String.valueOf(i);
final String goodsId=goodId;
final int num=nums;
new Thread(new Runnable() {
@Override
public void run() {
try{
// TODO Auto-generated method stub
//查找出用户要买的商品
SysKc goods = sysKcService.findById(goodsId);
//如果有这么多库存 -----重点在这判断,如果高并发会产生错误。
if(goods.getNum()>=num){
//模拟网络延时
// Thread.sleep(1000);
//先减去库存
// int a = sysKcService.reduceKc(num, goodsId);
sysKcService.reduceKc(num, goodsId);
sysKcOrderService.generateOrder(consumer, goodsId, num);
System.out.println(consumer+":购买成功");
//保存订单
// if(a!=0){
// }
}else{
System.out.println(consumer+":购买失败");
}
}catch(Exception e){
e.printStackTrace();
}
}
}).start();
}
System.out.println("结束");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
如果按照以上的调用方法和dao层更新商品库存数量的方法设计代码,则出现的结果是商品表中库存成为负数,订购表中出现大于库存的订购关系。
解决思路:
第一,利用数据库的行级锁。 就是利用Oracle,MySQL的行级锁–同一时间只有一个线程能够操作同一行记录,对Dao层更新库存的方法进行改造:
public int reduceNum1(int num,String id){
String sql = "update SysKc t set t.num=(t.num-?) where t.id=? and t.num>0 "; //---加上一句 and t.num>0,并且在控制层对返回的int值进行判断。
//Integer i = this.execute(sql,num,id);
return this.execute(sql,num,id);
}
public String SecKill() throws InterruptedException {
try {
// String consumer = request.getParameter("consumer");
String goodId = request.getParameter("goodsId");
int nums = Integer.parseInt(request.getParameter("num"));
for(int i=0;i<50;i++){
final String consumer="consumer"+String.valueOf(i);
final String goodsId=goodId;
final int num=nums;
new Thread(new Runnable() {
@Override
public void run() {
try{
// TODO Auto-generated method stub
//查找出用户要买的商品
SysKc goods = sysKcService.findById(goodsId);
//如果有这么多库存
if(goods.getNum()>=num){
//模拟网络延时
Thread.sleep(1000);
//先减去库存
int a = sysKcService.reduceKc(num, goodsId);
//保存订单
if(a!=0){ //重点在这行和上面的一行上。
sysKcOrderService.generateOrder(consumer, goodsId, num);
System.out.println(consumer+":购买成功");
}
}else{
System.out.println(consumer+":购买失败");
}
}catch(Exception e){
e.printStackTrace();
}
}
}).start();
}
System.out.println("结束");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
第二种方法利用同步锁的方式,在操作商品表和订购关系的方法上加锁。
public String SecKill() throws InterruptedException {
try {
// String consumer = request.getParameter("consumer");
String goodId = request.getParameter("goodsId");
int nums = Integer.parseInt(request.getParameter("num"));
for(int i=0;i<50;i++){
final String consumer="consumer"+String.valueOf(i);
final String goodsId=goodId;
final int num=nums;
new Thread(new Runnable() {
@Override
public void run() {
try{
// TODO Auto-generated method stub
//查找出用户要买的商品
order(goodsId,consumer,num);
}catch(Exception e){
e.printStackTrace();
}
}
}).start();
}
System.out.println("结束");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//加锁
public synchronized void order(String goodId,String consumer,int num) throws Exception{
SysKc goods = sysKcService.findById(goodId);
if(goods.getNum()>=num){
sysKcService.reduceKc(num, goodId);
sysKcOrderService.generateOrder(consumer, goodId, num);
System.out.println(consumer+":购买成功");
}else{
System.out.println(consumer+":购买失败");
}
}
利用上述的方法处理后,不管网络延迟是否,结果都是商品库存最后减少为0,订购关系和原始库存的数量一致。同步锁比较耗资源,所以建议利用行锁。