单体架构中
synchronized (this) { //原子代码 }synchronized是对对象实例加锁,在单体架构里面,只运行在一台jvm中,所以jvm可以判断是否对该对象加锁。
Redis分布式锁
①第一版
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("flag", "flag"); if (!aBoolean){ return "error"; } String stock = stringRedisTemplate.opsForValue().get("stock"); Integer integer = Integer.valueOf(stock); if (integer > 0) { integer -= 1; stringRedisTemplate.opsForValue().set("stock", integer.toString()); System.out.println("出库成功,仓库余额:" + integer); } else { System.out.println("库存不足!"); } stringRedisTemplate.delete("flag"); return "index";第一版我们用了setIfAbsent()
也就是jedis中的setnx
setnx (key ,value)
只在键
key
不存在的情况下, 将键key
的值设置为value
。若键
key
已经存在, 则SETNX
命令不做任何动作。存在问题:如果我们的一个线程刚好执行了Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("flag", "flag");
然后,这台服务器down了,这时候还没有执行stringRedisTemplate.delete("flag");
所以锁一直都在,导致了这个锁持续存在
②第二版
第一版存在的问题是这个锁可能一致都会存在,只有执行stringRedisTemplate.delete("flag");才会去除锁。
当是我们代码过程中抛出异常的问题的时候我们只需try...catch...
那么我们只需要对锁加时间
Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("flag", "flag",10L, TimeUnit.SECONDS); if (aBoolean){ return "error"; } try(){ String stock = stringRedisTemplate.opsForValue().get("stock"); Integer integer = Integer.valueOf(stock); if (integer > 0) { integer -= 1; stringRedisTemplate.opsForValue().set("stock", integer.toString()); System.out.println("出库成功,仓库余额:" + integer); } else { System.out.println("库存不足!"); } }catch(Exception e){ }finally{ stringRedisTemplate.delete("flag"); } return "index";这时候有的人会把
stringRedisTemplate.opsForValue().setIfAbsent("flag", "flag",10L, TimeUnit.SECONDS);写成两行
stringRedisTemplate.opsForValue().setIfAbsent("flag","flag"); stringRedisTemplate.expire("flag",10L,TimeUnit.SECONDS);这个还是第一个问题,我第一行执行完毕后服务器down了,所以这两行代码要保持原子性,所以redis给我们提供了第一个api
第二版存在的问题
如果我们逻辑代码的执行事件大于你加锁的时间怎么办,你加锁为10s中,但我这个程序执行了15s,这时候,就会存在原子代码还没有执行完,所就会自动失效,这时候第二个线程就会进来加锁,但一个线程接着向下执行了
stringRedisTemplate.delete("flag");那么第一个线程就会把第二个线程加的锁给释放了,在高并发的场景下,这就造成了锁失效的问题
③第三版
第二版存在的问题,锁失效。因为你加锁的时间小于我逻辑代码执行的时间,第一个线程删除了第二个线程新加的锁
如何解决,当每一个线程进来的时候,我给这个线程设置一个UUID
String lock = UUID.randomUUID().toString(); Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent("flag", lock,10L, TimeUnit.SECONDS); if (aBoolean){ return "error"; } try { String stock = stringRedisTemplate.opsForValue().get("stock"); nteger integer = Integer.valueOf(stock); if (integer > 0) { integer -= 1; stringRedisTemplate.opsForValue().set("stock", integer.toString()); System.out.println("出库成功,仓库余额:" + integer); } else { System.out.println("库存不足!"); } }catch (Exception e){} finally { if (lock.equals(stringRedisTemplate.opsForValue().get("flag"))){ stringRedisTemplate.delete("flag"); } } return "index"; 还是没有解决设置时间的问题
我们可一有一个子线程来实现一个定时器,每隔多少s监听一次,看看我们的锁如果还存在没就锁续上点时间,这样会造成线程拥堵
Redisson分布式锁
@Bean public Redisson redisson(){ Config config = new Config(); config.useSingleServer().setAddress("redis://192.168.80.138:7070").setDatabase(0); return (Redisson) Redisson.create(config); } RLock flag = redisson.getLock("flag"); try { flag.lock(); String stock = stringRedisTemplate.opsForValue().get("stock"); Integer integer = Integer.valueOf(stock); if (integer > 0) { integer -= 1; stringRedisTemplate.opsForValue().set("stock", integer.toString()); System.out.println("出库成功,仓库余额:" + integer); } else { System.out.println("库存不足!"); } }catch (Exception e){} finally { flag.unlock(); } return "index";
利用分段锁思想,将公共资源分解成多段进行抢购
1.导入maven依赖
<dependency> <groupId>org.redisson</groupId> <artifactId>redisson</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency> 2.自定义线程池
package com.zq.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.AsyncConfigurer; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; @Configuration public class MyExecutorConfiguration { @Bean public Executor myAsyncPool() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //核心线程池大小 executor.setCorePoolSize(5); //最大线程数 executor.setMaxPoolSize(5); //队列容量 executor.setQueueCapacity(150); // 活跃时间 executor.setKeepAliveSeconds(300); // 线程名字前缀 // executor.setThreadNamePrefix("MyExecutor-"); //设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,使异步线程的销毁优先于Redis等其他处理报错 executor.setWaitForTasksToCompleteOnShutdown(true); //设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住 executor.setAwaitTerminationSeconds(60); // setRejectedExecutionHandler:当pool已经达到max size的时候,且缓冲队列已满时,如何处理新任务 /*(1) 默认的ThreadPoolExecutor.AbortPolicy 处理程序遭到拒绝将抛出运行时 RejectedExecutionException; (2) ThreadPoolExecutor.CallerRunsPolicy 线程调用运行该任务的 execute 本身。此策略提供 简单的反馈控制机制,能够减缓新任务的提交速度 (3) ThreadPoolExecutor.DiscardPolicy 不能执行的任务将被删除; (4) ThreadPoolExecutor.DiscardOldestPolicy 如果执行程序尚未关闭,则位于工作队列头部的 任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。*/ executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy()); executor.initialize(); return executor; } } 3.自定义Redisson
@Configuration public class RedissonConfiguration { @Bean public Redisson redisson(){ Config config = new Config(); config.useSingleServer().setAddress("redis://192.168.37.3:6379").setDatabase(0); return (Redisson) Redisson.create(config); } }4.开启异步请求
启动类使用@EnableAsync,使@Async 生效
@SpringBootApplication @EnableAsync//使@Async 生效 public class JwtApplication { public static void main(String[] args) { SpringApplication.run(JwtApplication.class, args); } }
@Autowired Redisson redisson; @Autowired StringRedisTemplate redisTemplate; @Async("myAsyncPool") public Future<String> test() { String num = Thread.currentThread().getName().replace("myAsyncPool-", ""); RLock flag = null; try { flag = redisson.getLock("flag" + num); flag.lock(); String stock = redisTemplate.opsForValue().get("stock" + num); Integer integer =0; if (stock!=null && !"".equals(stock)){ integer = Integer.valueOf(stock); } if (integer > 0) { integer -= 1; redisTemplate.opsForValue().set("stock" + num, integer.toString()); System.out.println(num+"库出库成功,仓库余额:" + integer); if (integer==0) Thread.currentThread().sleep(3000); } else { // list.add(num); // Thread.currentThread().interrupt(); System.out.println(num+"库库存不足!"); } } catch (Exception e) { e.printStackTrace(); } finally { if (flag != null) flag.unlock(); } return new AsyncResult<>("任务一完成"); }