一分钟上手分布式锁

单体架构中

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<>("任务一完成");
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值