1.
效率最低(同步关键字,低并发可以,高并发不行,10000个线程,甚至十几秒钟才扣减一个库存)
for update 也是悲观锁,会锁住当前行,影响性能
1.乐观锁机制(加version版本号)
3.用mybatis自带的行锁,同一时间只能有一个线程修改某一行数据
直接上代码:
mapper代码:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.ProductDao">
<!--FOR UPDATE 悲观锁没用-->
<select id="getProductAmountById" resultType="com.example.entity.Product">
SELECT id ,amount,version FROM product WHERE id=1 FOR UPDATE;
</select>
<update id="subtracStock" >
UPDATE product set
amount = amount - #{buyAmount},
version = version+1
WHERE id=1
</update>
<update id="subtracStockWithOptimistic" >
UPDATE product set
amount = amount - #{buyAmount},
version = version+1
WHERE id=1 and version = #{version}
</update>
<!--不用乐观锁,用数据库提供的行锁来实现(强推)-->
<update id="subtracStockNoOptimistic" >
UPDATE product set
amount = amount - #{buyAmount}
WHERE id=1 and amount >= #{buyAmount}+0
</update>
dao:
import com.example.entity.Product;
import org.apache.ibatis.annotations.Param;
public interface ProductDao {
int subtracStock(@Param("buyAmount") int buyAmoun);
int subtracStockWithOptimistic(@Param("buyAmount") int buyAmount,@Param("version") int version);
Product getProductAmountById();
int subtracStockNoOptimistic(@Param("buyAmount") int buyAmoun);
}
service:
import org.apache.ibatis.annotations.Param;
public interface ProductService {
int subtracStock(Integer id, Integer buyAmout);
int subtracStockOptimistic(Integer id, Integer buyAmout);
int subtracStockNoOptimistic(int buyAmoun);
}
service 实现
package com.example.service;
import com.example.CustomException.NoStockException;
import com.example.entity.Product;
import com.example.mapper.ProductDao;
import com.example.redis.RedisUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class ProductServiceImp implements ProductService {
@Autowired
public ProductDao productDao;
@Autowired
private RedisUtils redisUtils;
//性能太差,并发不高的话可以使用(否则贼慢 10000个线程就不行了,库存减不动了)
@Override
public int subtracStock(Integer productId,Integer buyAmount) {
synchronized (ProductServiceImp.class){
//判断库存
Product product = productDao.getProductAmountById();
Integer amount = product.getAmount();
if(amount<=0){
throw new NoStockException("no stock",406,"no stock");
}
if(amount>=buyAmount){
productDao.subtracStock(buyAmount);
System.out.println("--------");
}else{
throw new NoStockException("no stock",406,"less stock");
}
}
return 0;
}
@Override
public int subtracStockOptimistic(Integer id, Integer buyAmount) {
//判断库存
Product product = productDao.getProductAmountById();
if(product.getAmount()<=0){
throw new NoStockException("no stock",406,"no stock");
}
if(product.getAmount()>=buyAmount){
//错误:获取最新版本号,一样则进行修改(这个习惯错误)
//正确:将 商品id,购物数量,自己的version 交给数据库执行,判断交给数据库(高效,不容易犯错)
int result = productDao.subtracStockWithOptimistic(buyAmount,product.getVersion());
if(result == 1){
return 1;
}else{
//进入重试阶段(为的是刚好有200的并发,200的库存,将库存都卖完,而不是200 线程进来,因为版本号不一致,放弃了扣减库存的机会)
subtracStockOptimistic( id, buyAmount);
}
}else{
throw new NoStockException("no stock",406,"less stock");
}
return 0;
}
@Override
public int subtracStockNoOptimistic(int buyAmoun) {
//一分钟之内没有秒杀过的用户可以秒杀,否则不让秒杀
Long start = System.currentTimeMillis();
Object isExist = redisUtils.get("hasGetStock");
if(null == isExist){
//不需要重试也可以200个请求都把100 的库存减去
int result = productDao.subtracStockNoOptimistic(buyAmoun);
//秒杀成功,将秒杀结果存到redis,防止同一个用户多次秒杀(薅羊毛)
if(result ==0){
redisUtils.set("hasGetStock",1,1L,TimeUnit.MINUTES);
return 1;
}
}else{
// redis 的ttl还可以做倒计时功能
System.out.println("一分钟之内不可以多次秒杀");
return 0;
}
return 0;
}
}
controller:
//减库存
@GetMapping("/submitOrder")
public String submitOrder(Integer amount) {
try {
// 悲观锁并发低可以,并发量大的话,就严重影响性能(10000并发 扣库存很慢,甚至十几秒扣一个库存)
//int a = productService.subtracStock(1,amount);
//乐观锁(推荐)
//productService.subtracStockOptimistic(1,amount);
//不用乐观锁(强推)
productService.subtracStockNoOptimistic(amount);
//问题:如何防止被恶意用户薅羊毛??(限制用户抢的次数)
//成功后,由mq来生成订单(mq的好处:异步,可以提高用户响应时间)
} catch(NoStockException nse) {
throw new NoStockException("no stock",406,"no stock");
} catch(Exception e) {
throw new RuntimeException("Failed!!");
}
// 告知微信(支付宝)已经成功处理,不需要再发送回调
return "success";
}
总结:
第三种方法性能最好,实现也简单
来源:CSDN
作者:qq_38807606
链接:https://blog.csdn.net/qq_38807606/article/details/104582349