//利用redis 做分布式锁,此处实现需要根据实际场景进行优化. 好处是能分担数据库压力,但加锁的时间无法确定,需要另启线程进行延时处理//有个问题是 如果子线程延期6次后 主线程还未运行完毕 后续又会引发很多问题,这里可以 结合case1的乐观锁一起使用,用version字段双重保险,或许本来就应该这么做
static ThreadPoolExecutor pool = new ThreadPoolExecutor(10,20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(20));//这里最高并发限制50,我这里只有拿到锁了 才开启线程,早已有余,商品种类多,可以考虑加大队列/最大线程数
static{
pool.prestartAllCoreThreads();//预先创建核心线程
}
@GetMapping("/payII")public String testPayLockByRedis() throwsInterruptedException {//这里最好还是用userId
String name =Thread.currentThread().getName();for (int i = 0; i < 5; i++) {
Boolean lock= redisTemplate.opsForValue().setIfAbsent("1", name, 10000, TimeUnit.MILLISECONDS);if(lock) {
MyRunnable m= newMyRunnable(name);try{
pool.execute(m);
Goods goods= goodsMapper.selectByPrimaryKey(1);int storage =goods.getGoodsStorage();
goods.setGoodsStorage(storage- 1);if (goods.getGoodsStorage() >= 0) {
goodsMapper.updateByPrimaryKey(goods);
log.info("本次购买商品一件,剩余库存{}件", goods.getGoodsStorage());return "本次购买商品一件,剩余库存" + goods.getGoodsStorage() + "件";
}else{
log.info("库存不足");return "库存不足";
}
}catch(Exception e) {//这里抛出异常让事务回滚 , 异常部分让切面处理,优先级和事务一致,优先级一致事务先执行
throw newRunTimeException(e);
}finally{
m.flag= false;//这里的处理是因为 子线程在延时6次后,没有中断主线程的运行(这里无法中断,线程之间的运行是独立的,子线程抛出异常无法被主线程捕获,至多让thread设置一个//UncaughtExceptionHandler,在子线程抛出异常后,子线程内部自己进行捕获处理逻辑,然而还是不能影响主线程),既然如此,存在30s过后主线程仍未执行完毕的可能性,此时锁已易主,如果未//和version字段一起做保险处理,建议抛出异常,回滚事务
Object o = redisTemplate.opsForValue().get("1")if (o!=null &&name.equals(o.toString())) {
redisTemplate.delete("1");
}else{throw new RuntimeException("锁已失效")}
}
}else{continue;
}
}return "网络延迟,请稍后尝试";
}private class MyRunnable implementsRunnable {boolean flag = true;int time; //延期次数
String name; //线程name 用userId好些
private MyRunnable(String name) {this.name =name;}
@Overridepublic voidrun() {try{//给予一定的处理时间 再给任务做延时处理
Thread.sleep(2000);while (flag && time++ < 6) {
Object o= redisTemplate.opsForValue().get("1");//锁存在 且是自身加的锁 给锁延期
if (o != null && o.toString().equals(name) &&flag) {//如果出现判定通过
/*case1: 外面修改flag 这里延期成功 外面删除 并不影响
case2: 外面修改flag 删除 还未往redis 重新set 这里就延期 也不影响
case3: 外面修改flag 删除 其他用户往redis 重新set 这里再延期 好像也不会发生什么*/redisTemplate.expire("1", 10000, TimeUnit.MILLISECONDS);
System.out.println("延时一次");
}
Thread.sleep(3000);
}
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}