前言

前面已经写了Redisson大多的内容,我们再看看Redisson官网共有哪些组件:

image.png

剩下还有Semaphore和CountDownLatch两块,我们就趁热打铁,赶紧看看Redisson是如何实现的吧。

我们在JDK中都知道Semaphore和CountDownLatch两兄弟,这里就不多赘述,不了解的可以再回头看看。

Semaphore使用示例

先看下Semaphore原理图如下:

image.png

接着我们看下Redisson中使用的案例:

RSemaphore semaphore = redisson.getSemaphore("semaphore");
// 同时最多允许3个线程获取锁
semaphore.trySetPermits(3); for(int i = 0; i < 10; i++) {
  new Thread(new Runnable() {     @Override
    public void run() {
      try {
        System.out.println(new Date() + ":线程[" + Thread.currentThread().getName() + "]尝试获取Semaphore锁"); 
        semaphore.acquire();
        System.out.println(new Date() + ":线程[" + Thread.currentThread().getName() + "]成功获取到了Semaphore锁,开始工作"); 
        Thread.sleep(3000);  
        semaphore.release();
        System.out.println(new Date() + ":线程[" + Thread.currentThread().getName() + "]释放Semaphore锁"); 
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }).start();
}

Semaphore源码解析

接着我们根据上面的示例,看看源码是如何实现的:

第一步:
semaphore.trySetPermits(3);

public class RedissonSemaphore extends RedissonExpirable implements RSemaphore {
    @Override
    public boolean trySetPermits(int permits) {
        return get(trySetPermitsAsync(permits));
    }     @Override
    public RFuture<Boolean> trySetPermitsAsync(int permits) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "local value = redis.call('get', KEYS[1]); " +
                "if (value == false or value == 0) then "
                    + "redis.call('set', KEYS[1], ARGV[1]); "
                    + "redis.call('publish', KEYS[2], ARGV[1]); "
                    + "return 1;"
                + "end;"
                + "return 0;",
                Arrays.<Object>asList(getName(), getChannelName()), permits);
    } }

执行流程为:

  1. get semaphore,获取到一个当前的值
  2. 第一次数据为0, 然后使用set semaphore 3,将这个信号量同时能够允许获取锁的客户端的数量设置为3
  3. 然后发布一些消息,返回1

接着看看semaphore.acquire();semaphore.release(); 逻辑:

public class RedissonSemaphore extends RedissonExpirable implements RSemaphore {
    @Override
    public RFuture<Boolean> tryAcquireAsync(int permits) {
        if (permits < 0) {
            throw new IllegalArgumentException("Permits amount can't be negative");
        }
        if (permits == 0) {
            return RedissonPromise.newSucceededFuture(true);
        }         return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                  "local value = redis.call('get', KEYS[1]); " +
                  "if (value ~= false and tonumber(value) >= tonumber(ARGV[1])) then " +
                      "local val = redis.call('decrby', KEYS[1], ARGV[1]); " +
                      "return 1; " +
                  "end; " +
                  "return 0;",
                  Collections.<Object>singletonList(getName()), permits);
    }     @Override
    public RFuture<Void> releaseAsync(int permits) {
        if (permits < 0) {
            throw new IllegalArgumentException("Permits amount can't be negative");
        }
        if (permits == 0) {
            return RedissonPromise.newSucceededFuture(null);
        }         return commandExecutor.evalWriteAsync(getName(), StringCodec.INSTANCE, RedisCommands.EVAL_VOID,
            "local value = redis.call('incrby', KEYS[1], ARGV[1]); " +
            "redis.call('publish', KEYS[2], value); ",
            Arrays.<Object>asList(getName(), getChannelName()), permits);
    } }

先看看加锁的逻辑tryAcquireAsync()

  1. get semaphore,获取到一个当前的值,比如说是3,3 > 1
  2. decrby semaphore 1,将信号量允许获取锁的客户端的数量递减1,变成2
  3. decrby semaphore 1
  4. decrby semaphore 1
  5. 执行3次加锁后,semaphore值为0

此时如果再来进行加锁则直接返回0,然后进入死循环去获取锁,如下图:

image.png

接着看看解锁逻辑releaseAsync()

  1. incrby semaphore 1,每次一个客户端释放掉这个锁的话,就会将信号量的值累加1,信号量的值就不是0了

看到这里大家就明白了了,Redisson实现Semaphore其实是很简单了

CountDownLatch使用示例

使用案例:

RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
latch.trySetCount(3);
System.out.println(new Date() + ":线程[" + Thread.currentThread().getName() + "]设置了必须有3个线程执行countDown,进入等待中。。。");  for(int i = 0; i < 3; i++) {
  new Thread(new Runnable() {     @Override
    public void run() {
      try {
        System.out.println(new Date() + ":线程[" + Thread.currentThread().getName() + "]在做一些操作,请耐心等待。。。。。。"); 
        Thread.sleep(3000); 
        RCountDownLatch localLatch = redisson.getCountDownLatch("anyCountDownLatch");
        localLatch.countDown();
        System.out.println(new Date() + ":线程[" + Thread.currentThread().getName() + "]执行countDown操作"); 
      } catch (Exception e) {
        e.printStackTrace(); 
      }
    }   }).start();
} latch.await();
System.out.println(new Date() + ":线程[" + Thread.currentThread().getName() + "]收到通知,有3个线程都执行了countDown操作,可以继续往下走"); 

CountDownLatch 源码解析

源码如下:

 public class RedissonCountDownLatch extends RedissonObject implements RCountDownLatch {

    @Override
    public RFuture<Boolean> trySetCountAsync(long count) {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                "if redis.call('exists', KEYS[1]) == 0 then "
                    + "redis.call('set', KEYS[1], ARGV[2]); "
                    + "redis.call('publish', KEYS[2], ARGV[1]); "
                    + "return 1 "
                + "else "
                    + "return 0 "
                + "end",
                Arrays.<Object>asList(getName(), getChannelName()), newCountMessage, count);
    }     @Override
    public RFuture<Void> countDownAsync() {
        return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
                        "local v = redis.call('decr', KEYS[1]);" +
                        "if v <= 0 then redis.call('del', KEYS[1]) end;" +
                        "if v == 0 then redis.call('publish', KEYS[2], ARGV[1]) end;",
                    Arrays.<Object>asList(getName(), getChannelName()), zeroCountMessage);
    } }

先分析trySetCount()方法逻辑:

  1. exists anyCountDownLatch,第一次肯定是不存在的
  2. set redisson_countdownlatch__channel__anyCountDownLatch 3
  3. 返回1

接着分析latch.await();方法,如下图:

image.png

这个方法其实就是陷入一个while true死循环,不断的get anyCountDownLatch的值,如果这个值还是大于0那么就继续死循环,否则的话呢,就退出这个死循环

最后分析localLatch.countDown();方法:

  1. decr anyCountDownLatch,就是每次一个客户端执行countDown操作,其实就是将这个cocuntDownLatch的值递减1
  2. await()方面已经分析过,死循环去判断anyCountDownLatch对应存储的值是否为0,如果为0则接着执行自己的逻辑

总结

看到了这里 这两个组件是不是很简单?

到了这里,Redisson部分的学习都已经结束了,后面还会学习ZK实现分布式锁的原理。

申明

本文章首发自本人博客:https://www.cnblogs.com/wang-meng 和公众号:壹枝花算不算浪漫,如若转载请标明来源!

感兴趣的小伙伴可关注个人公众号:壹枝花算不算浪漫

【分布式锁】05-使用Redisson中Semaphore和CountDownLatch原理的更多相关文章

  1. Redisson 分布式锁源码 11:Semaphore 和 CountDownLatch

    前言 Redisson 除了提供了分布式锁之外,还额外提供了同步组件,Semaphore 和 CountDownLatch. Semaphore 意思就是在分布式场景下,只有 3 个凭证,也就意味着同 ...

  2. [转帖]分布式锁-redLock And Redisson

    分布式锁-redLock And Redisson 2019-03-01 16:51:48 淹不死的水 阅读数 372更多 分类专栏: 分布式锁   版权声明:本文为博主原创文章,遵循CC 4.0 B ...

  3. Springboot分别使用乐观锁和分布式锁(基于redisson)完成高并发防超卖

    原文 :https://blog.csdn.net/tianyaleixiaowu/article/details/90036180 乐观锁 乐观锁就是在修改时,带上version版本号.这样如果试图 ...

  4. 来吧,展示!Redis的分布式锁及其实现Redisson的全过程

    前言 分布式锁是控制分布式系统之间同步访问共享资源的一种方式. 在分布式系统中,常常需要协调他们的动作.如果不同的系统或是同一个系统的不同主机之间共享了一个或一组资源,那么访问这些资源的时候,往往需要 ...

  5. 基于redis的分布式锁实现方案--redisson

    实例代码地址,请前往:https://gitee.com/GuoqingLee/distributed-seckill redis官方文档地址,请前往:http://www.redis.cn/topi ...

  6. Java并发包中Semaphore的工作原理、源码分析及使用示例

    1. 信号量Semaphore的介绍 我们以一个停车场运作为例来说明信号量的作用.假设停车场只有三个车位,一开始三个车位都是空的.这时如果同时来了三辆车,看门人允许其中它们进入进入,然后放下车拦.以后 ...

  7. 【分布式锁】07-Zookeeper实现分布式锁:Semaphore、读写锁实现原理

    前言 前面已经讲解了Zookeeper可重入锁的实现原理,自己对分布式锁也有了更深的认知. 我在公众号中发了一个疑问,相比于Redis来说,Zookeeper的实现方式要更好一些,即便Redis作者实 ...

  8. ZooKeeper 分布式锁 Curator 源码 01:可重入锁

    前言 一般工作中常用的分布式锁,就是基于 Redis 和 ZooKeeper,前面已经介绍完了 Redisson 锁相关的源码,下面一起看看基于 ZooKeeper 的锁.也就是 Curator 这个 ...

  9. ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放

    ZooKeeper 分布式锁 Curator 源码 02:可重入锁重复加锁和锁释放 前言 加锁逻辑已经介绍完毕,那当一个线程重复加锁是如何处理的呢? 锁重入 在上一小节中,可以看到加锁的过程,再回头看 ...

随机推荐

  1. 最简化的DirectX 11开发环境的配置 VS2010

    转载自:http://blog.csdn.net/zhmxy555/article/details/7672101 在编写基于DirectX 11的应用程序之前,我们当然需要在IDE中加入Direct ...

  2. Nginx笔记总结六:Nginx location配置

    语法规则:location [= | ~ | ~* | ^~] /uri/ {....} = 表示精确匹配 ^~ 表示uri以某个常规字符串开头 ~ 表示区分大小写的正则表达式 ~* 表示不区分大小写 ...

  3. Android开发之《ffmpeg解码mjpeg视频流》

    MJPEG格式和码流分析,MJPEG格式的一些简介 FFmpeg解码USB摄像头MJPEG输出:http://blog.csdn.net/light_in_dark/article/details/5 ...

  4. IT男频繁猝死背后的心理探秘

    "深圳36岁IT男猝死酒店马桶上"这条新闻再次成为人们眼球的焦点,每每发生这样的事情,难免让人扼腕唏嘘,他们本该是风华正茂的年纪,家有老母贤妻爱子,甚至房子车子票子都不缺,该是一边 ...

  5. Luogu_1280_尼克的任务

    题目描述 尼克每天上班之前都连接上英特网,接收他的上司发来的邮件,这些邮件包含了尼克主管的部门当天要完成的全部任务,每个任务由一个开始时刻与一个持续时间构成. 尼克的一个工作日为N分钟,从第一分钟开始 ...

  6. mac下配置开发环境

    常用命令 显示隐藏文件 1 defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder 关闭隐藏文 ...

  7. 码海拾遗:基于MySQL Connector/C++的MySQL操作(连接池)

    1.MySQL安装及简单设置 (1)安装:在OSX系统下,可以使用万能的“brew install”命令来进行安装:brew isntall mysql(默认安装最新版的MySQL) (2)启动:br ...

  8. 添砖加瓦:snappy无损压缩算法

    一.简介 Snappy(旧称:Zippy)是Google基于LZ77的思路用C++语言编写的快速数据压缩与解压程序库,并在2011年开源.其目标并非最大压缩率或与其他压缩程序的兼容性,而是非常高的速度 ...

  9. 手机预装APP“死灰复燃”,这颗“毒瘤”到底怎么了

    ​ ​ 离全新智能手机集中发布的8月底.9月初这个时间段越来越近了,iPhone 8等重磅新机也为互联网媒体贡献了足够的流量和热度.但就在大众聚焦于新机时,一个困扰很多人的问题再度冒出头--智能手机上 ...

  10. Golang: chan定义问题(7)

    通常都是定义读写双向的 chan,定义单向 chan 问题. 专栏的介绍可以参考 <GotchaGolang专栏>,代码可以看<宝库-Gotcha>. 通过 只写 chan 传 ...