前言

在Java并发编程中,咱们通常应用到synchronized 、Lock这两个线程锁,Java中的锁,只能保障对同一个JVM中的线程无效。而在分布式集群环境,这个时候咱们就须要应用到分布式锁。

实现分布式锁的计划

  • 基于数据库实现分布式锁
  • 基于缓存Redis实现分布式锁
  • 基于Zookeeper的长期序列化节点实现分布式锁

Redis实现分布式锁

场景:在高并发的状况下,可能有大量申请来到数据库查问三级分类数据,而这种数据不会常常扭转,能够引入缓存来存储第一次从数据库查问进去的数据,其余线程就能够去缓存中获取数据,来缩小数据库的查问压力。

在集群的环境下,就能够应用分布式锁来管制去查询数据库的次数。

阶段一

    private  Map<String, List<Catelog2Vo>>  getCatalogJsonDBWithRedisLock() {        // 去Redis中抢占地位        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111");        if (lock){            // 抢到锁了 执行业务            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();            // 删除锁            stringRedisTemplate.delete("lock");            return dataFromDb;        }else {            // 自旋获取锁            // 休眠100ms            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            return getCatalogJsonDBWithRedisLock();        }    }
失去锁当前,咱们应该再去缓存中确定一次,如果没有才须要持续查问,从数据库查到数据当前,应该先把数据放入缓存中,再将数据返回。
  private Map<String, List<Catelog2Vo>> getDataFromDb() {        // 失去锁当前,咱们应该再去缓存中确定一次,如果没有才须要持续查问        String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");        if (!StringUtils.isEmpty(catalogJson)) {            // 反序列化 转换为指定对象            Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new             TypeReference<Map<String, List<Catelog2Vo>>>() {});            return result;        }        System.out.println("查询数据库了......");        // 查问所有分类数据在进行刷选        List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);        // 查问一级分类        List<CategoryEntity> leave1Categorys = getParent_cid(categoryEntityList, 0L);        Map<String, List<Catelog2Vo>> listMap = leave1Categorys.stream().collect(Collectors.toMap(k -> k.getCatId().toString(), l1 -> {            List<CategoryEntity> categoryL2List = getParent_cid(categoryEntityList, l1.getCatId());            List<Catelog2Vo> catelog2Vos = null;            if (categoryL2List != null) {                catelog2Vos = categoryL2List.stream().map(l2 -> {                    Catelog2Vo catelog2Vo = new Catelog2Vo(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());                    List<CategoryEntity> categoryL3List = getParent_cid(categoryEntityList,                                                                         l2.getCatId());                    if (categoryL3List != null) {                        List<Catelog2Vo.Catelog3Vo> catelog3Vos =                              categoryL3List.stream().map(l3 -> {                            Catelog2Vo.Catelog3Vo catelog3Vo = new                                Catelog2Vo.Catelog3Vo(l2.getCatId().toString(),                                                       l3.getCatId().toString(),                                                       l3.getName());                            return catelog3Vo;                        }).collect(Collectors.toList());                        catelog2Vo.setCatalog3List(catelog3Vos);                    }                    return catelog2Vo;                }).collect(Collectors.toList());            }            return catelog2Vos;        }));        // 最初需将数据退出的缓存中        String jsonString = JSON.toJSONString(listMap);        stringRedisTemplate.opsForValue().set("catalogJson", jsonString, 1L,                                               TimeUnit.DAYS);        return listMap;    }
阶段二

    private  Map<String, List<Catelog2Vo>>  getCatalogJsonDBWithRedisLock() {        // 去Redis中抢占地位        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", "1111");        if (lock){            // 抢到锁了 执行业务            // 设置过期工夫            stringRedisTemplate.expire("lock",3,TimeUnit.SECONDS);            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();            // 删除锁            stringRedisTemplate.delete("lock");            return dataFromDb;        }else {            // 自旋获取锁            // 休眠100ms            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            return getCatalogJsonDBWithRedisLock();        }    }
阶段三

  private  Map<String, List<Catelog2Vo>>  getCatalogJsonDBWithRedisLock() {        // 去Redis中抢占地位  保障原子性      Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock",       "1111",300,TimeUnit.SECONDS);        if (lock){            // 抢到锁了 执行业务            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();            // 删除锁            stringRedisTemplate.delete("lock");            return dataFromDb;        }else {            // 自旋获取锁            // 休眠100ms            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            return getCatalogJsonDBWithRedisLock();        }    }
阶段四

private  Map<String, List<Catelog2Vo>>  getCatalogJsonDBWithRedisLock() {        String uuid = UUID.randomUUID().toString();        // 去Redis中抢占地位  保障原子性        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);        if (lock){            // 抢到锁了 执行业务            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();            String s = stringRedisTemplate.opsForValue().get("lock");            if (uuid.equals(s)){                // 删除锁                stringRedisTemplate.delete("lock");            }            return dataFromDb;        }else {            // 自旋获取锁            // 休眠100ms            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            return getCatalogJsonDBWithRedisLock();        }    }
阶段五

  private  Map<String, List<Catelog2Vo>>  getCatalogJsonDBWithRedisLock() {        String uuid = UUID.randomUUID().toString();        // 去Redis中抢占地位  保障原子性        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300,TimeUnit.SECONDS);        if (lock){            // 抢到锁了 执行业务            Map<String, List<Catelog2Vo>> dataFromDb = getDataFromDb();            String s = stringRedisTemplate.opsForValue().get("lock");            // 获取值比照+比照胜利删除=原子操作 Lua脚本解锁            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1] then\n" +                    "    return redis.call(\"del\",KEYS[1])\n" +                    "else\n" +                    "    return 0\n" +                    "end";            Long lock1 = stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class) , Arrays.asList("lock"), uuid);            return dataFromDb;        }else {            // 自旋获取锁            // 休眠100ms            try {                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            return getCatalogJsonDBWithRedisLock();        }    }
小总结
  1. stringRedisTemplate.opsForValue().setIfAbsent(“lock”, uuid,300,TimeUnit.SECONDS);
  2. stringRedisTemplate.execute(new DefaultRedisScript(script, Long.class) , Arrays.asList(“lock”), uuid);
  3. 应用Redis来实现分布式锁需保障加锁【占位+过期工夫】和删除锁【判断+删除】操作的原子性。
  4. Redis锁的过期工夫小于业务的执行工夫该如何主动续期?

    • 设置一个比业务耗时更长的过期工夫
    • Redisson的看门狗机制

Redisson实现分布式锁

Redisson 简介

Redisson是一个在Redis的根底上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java罕用对象,还提供了许多分布式服务。其中包含(BitSetSetMultimapSortedSetMapListQueueBlockingQueueDequeBlockingDequeSemaphoreLockAtomicLongCountDownLatchPublish / SubscribeBloom filterRemote serviceSpring cacheExecutor serviceLive Object serviceScheduler service) Redisson提供了应用Redis的最简略和最便捷的办法。Redisson的主旨是促成使用者对Redis的关注拆散(Separation of Concern),从而让使用者可能将精力更集中地放在解决业务逻辑上。

原理机制

集成Spring Boot 我的项目

  1. 引入依赖 【可引入Spring Boot 封装好的starter】

      <!-- https://mvnrepository.com/artifact/org.redisson/redisson -->        <dependency>            <groupId>org.redisson</groupId>            <artifactId>redisson</artifactId>            <version>3.12.0</version>        </dependency>
  2. 增加配置类

    @Configurationpublic class MyRedissonConfig {    @Bean(destroyMethod = "shutdown")    public RedissonClient redissonClient(){        // 创立配置  记得加redis://        Config config = new Config();        config.useSingleServer().setAddress("redis://192.168.26.104:6379");        // 依据配置创立RedissClient客户端        RedissonClient redissonClient = Redisson.create(config);        return redissonClient;    }}
可重入锁 Reentrant Lock
  1. 获取一把锁 redissonClient.getLock(“my-lock”);
  2. 给业务代码加锁 lock.lock();
  3. 解锁 lock.unlock();
  4. 看门狗机制 锁会主动续期
@ResponseBody    @GetMapping("/hello")    public String hello(){        // 1、获取一把锁,只有锁的名字一样,就是同一把锁        RLock lock = redissonClient.getLock("my-lock");        // 加锁        // 阻塞式期待,默认加的锁都是【看门狗工夫】30s工夫        //1)、锁的主动续期,如果业务超长,运行期间主动给锁续上新的30s,不必放心业务工夫长,锁主动过期被删掉        //2)、加锁的业务只有运行实现,就不会给以后锁续期,即便不手动解锁,锁默认在30s当前主动删除        lock.lock();        try {            System.out.println("加锁胜利......."+Thread.currentThread().getId());            Thread.sleep(30000);        } catch (InterruptedException e) {        }finally {            // 开释锁   不会呈现死锁状态 如果没有执行解锁,锁有过期工夫,过期了会将锁删除            lock.unlock();            System.out.println("解锁胜利......"+Thread.currentThread().getId());        }        return "hello";    }
lock办法有一个重载办法 lock(long leaseTime, TimeUnit unit)
  public void lock(long leaseTime, TimeUnit unit) {        try {            this.lock(leaseTime, unit, false);        } catch (InterruptedException var5) {            throw new IllegalStateException();        }    }

留神:指定了过期工夫后,不会进行主动续期,此时如果有多个线程,即使业务还然在执行,过期工夫到了之后,锁就会被开释,其余线程就会争抢到锁。

二个办法比照

  1. 如果设置了过期工夫,就会产生执行脚本给Redis,进行占锁,设置过期工夫为咱们指定的工夫。
  2. 未设置过期工夫,就会应用看门狗的默认工夫LockWatchdogTimeout 30*1000
  3. 只有没有指定过期的工夫的办法才有主动续期性能
  4. 主动续期实现机制 :只有占锁胜利,就会主动启动一个定时工作【从新给锁设置过期工夫,新的过期工夫就是看门狗的默认工夫】,每隔10s【( internalLockLeasTime)/3】都会主动续期。
  5. 持有锁的机器宕机问题,因为来不及续期,所以锁主动被开释,当该机再次复原时,因为其后盾守护线程是ScheduleTask,所以复原后会马上执行一次watchDog续期逻辑,执行过程中,它会感知到本人曾经失落了锁,所以不存在独特持有的问题。
读写锁 ReadWriteLock

保障肯定能读到最新数据,批改期间,写锁是一个互斥锁,读锁是一个共享锁。

  1. 写+读 写锁没有开释,读锁就得期待
  2. 写+写 阻塞形式
  3. 读+写 写锁期待读锁开释能力加锁
  4. 读+读 相当于无锁,并发读
 @ResponseBody    @GetMapping("/write")    public String writeLock(){        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");        RLock rLock = readWriteLock.writeLock();        String s = "";        try {            rLock.lock();            System.out.println("写锁加锁胜利......"+Thread.currentThread().getId());            s = UUID.randomUUID().toString();            stringRedisTemplate.opsForValue().set("writeLock",s);            Thread.sleep(30000);        } catch (InterruptedException e) {            e.printStackTrace();        }finally {            rLock.unlock();            System.out.println("写锁开释胜利......"+Thread.currentThread().getId());        }       return s;    }    @ResponseBody    @GetMapping("/read")    public String readLock(){        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("rw-lock");        RLock rLock = readWriteLock.readLock();        rLock.lock();        String s = "";        try{            System.out.println("读锁加锁胜利......"+Thread.currentThread().getId());            s = stringRedisTemplate.opsForValue().get("writeLock");        }catch (Exception e){            e.printStackTrace();        }finally {            System.out.println("读锁开释胜利......"+Thread.currentThread().getId());            rLock.unlock();        }        return s;    }
信号量 Semaphore

应用信号量来做分布式限流

    @ResponseBody    @GetMapping("/park")    public String park() throws InterruptedException {        RSemaphore park = redissonClient.getSemaphore("park");        // 抢占一个车位        boolean b = park.tryAcquire();        // 如果还能够抢占到 就执行业务代码        if (b){            // 执行业务代码        }else {            return "error";        }        return "ok=>"+b;    }    @ResponseBody    @GetMapping("/go")    public String go() {        RSemaphore park = redissonClient.getSemaphore("park");        //  开释一个车位        park.release();        return "ok";    }
闭锁 CountDownLatch

模仿场景:期待班级放学走了,保安关校门。

    @ResponseBody    @GetMapping("/lockdoor")    public String lockDoor() throws InterruptedException {        RCountDownLatch door = redissonClient.getCountDownLatch("door");        door.trySetCount(5);        // 期待闭锁实现        door.await();        return "放假了.....";    }    @GetMapping("/gogogo/{id}")    @ResponseBody    public String gogogo(@PathVariable("id")Long id){        RCountDownLatch door = redissonClient.getCountDownLatch("door");        // 计数减一        door.countDown();        return id+"班级走了....";    }
Redisson解决下面Redis查问问题
  /**     * 应用Redisson分布式锁来实现多个服务共享同一缓存中的数据     * @return     */    private Map<String, List<Catelog2Vo>> getCatalogJsonDBWithRedissonLock() {        RLock lock = redissonClient.getLock("catalogJson-lock");         // 该办法会阻塞其余线程向下执行,只有开释锁之后才会接着向下执行        lock.lock();        Map<String, List<Catelog2Vo>> dataFromDb = null;        try {            dataFromDb = getDataFromDb();        }finally {            lock.unlock();        }        return dataFromDb;    }

最初

欢送关注公众号:前程有光,支付一线大厂Java面试题总结+各知识点学习思维导+一份300页pdf文档的Java外围知识点总结!