锁:分布式的锁全局同步,这意味着任何一个时间点不会有两个客户端都拥有相同的锁。

1.可重入锁Shared Reentrant Lock

    首先我们先看一个全局可重入的锁(可以多次获取,不会被阻塞)。Shared意味着锁是全局可见的,客户端都可以请求锁。Reentrant和JDK的ReentrantLock类似,意味着同一个客户端在拥有锁的同时,可以多次获取,不会被阻塞。
1.可重入锁相关类介绍
    它是由类InterProcessMutex来实现。它的主要方法:
    1. // 构造方法
    1. public InterProcessMutex(CuratorFramework client, String path)
    1. public InterProcessMutex(CuratorFramework client, String path, LockInternalsDriver driver)
    1. // 通过acquire获得锁,并提供超时机制:
    1. public void acquire() throws Exception
    1. public boolean acquire(long time, TimeUnit unit) throws Exception
    1. // 撤销锁
    1. public void makeRevocable(RevocationListener<InterProcessMutex> listener)
    1. public void makeRevocable(final RevocationListener<InterProcessMutex> listener, Executor executor)
错误处理:还是强烈推荐你使用ConnectionStateListener处理连接状态的改变。当连接LOST时你不再拥有锁。
2.编写示例程序
    首先让我们创建一个模拟的共享资源, 这个资源期望只能单线程的访问,否则会有并发问题。
    1. public class FakeLimitedResource
    1. {
    1. private final AtomicBoolean inUse = new AtomicBoolean(false);
    1. // 模拟只能单线程操作的资源
    1. public void use() throws InterruptedException
    1. {
    1. if (!inUse.compareAndSet(false, true))
    1. {
    1. // 在正确使用锁的情况下,此异常不可能抛出
    1. throw new IllegalStateException("Needs to be used by one client at a time");
    1. }
    1. try
    1. {
    1. Thread.sleep((long) (3 * Math.random()));
    1. }
    1. finally
    1. {
    1. inUse.set(false);
    1. }
    1. }
    1. }
    然后创建一个ExampleClientThatLocks类,它负责请求锁,使用资源,释放锁这样一个完整的访问过程。
    1. public class ExampleClientThatLocks
    1. {
    1. private final InterProcessMutex lock;
    1. private final FakeLimitedResource resource;
    1. private final String clientName;
    1. public ExampleClientThatLocks(CuratorFramework client, String lockPath, FakeLimitedResource resource, String clientName)
    1. {
    1. this.resource = resource;
    1. this.clientName = clientName;
    1. lock = new InterProcessMutex(client, lockPath);
    1. }
    1. public void doWork(long time, TimeUnit unit) throws Exception
    1. {
    1. if (!lock.acquire(time, unit))
    1. {
    1. throw new IllegalStateException(clientName + " 不能得到互斥锁");
    1. }
    1. try
    1. {
    1. System.out.println(clientName + " 已获取到互斥锁");
    1. resource.use(); // 使用资源
    1. Thread.sleep(1000 * 1);
    1. }
    1. finally
    1. {
    1. System.out.println(clientName + " 释放互斥锁");
    1. lock.release(); // 总是在finally中释放
    1. }
    1. }
    1. }
    最后创建主程序来测试:
    1. public class InterProcessMutexExample
    1. {
    1. private static final int QTY = 5;
    1. private static final int REPETITIONS = QTY * 10;
    1. private static final String PATH = "/examples/locks";
    1. public static void main(String[] args) throws Exception
    1. {
    1. final FakeLimitedResource resource = new FakeLimitedResource();
    1. final List<CuratorFramework> clientList = new ArrayList<CuratorFramework>();
    1. for (int i = 0; i < QTY; i++)
    1. {
    1. CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", new ExponentialBackoffRetry(1000, 3));
    1. client.start();
    1. clientList.add(client);
    1. }
    1. System.out.println("连接初始化完成!");
    1. ExecutorService service = Executors.newFixedThreadPool(QTY);
    1. for (int i = 0; i < QTY; ++i)
    1. {
    1. final int index = i;
    1. Callable<Void> task = new Callable<Void>()
    1. {
    1. @Override
    1. public Void call() throws Exception
    1. {
    1. try
    1. {
    1. final ExampleClientThatLocks example = new ExampleClientThatLocks(clientList.get(index), PATH, resource, "Client " + index);
    1. for (int j = 0; j < REPETITIONS; ++j)
    1. {
    1. example.doWork(10, TimeUnit.SECONDS);
    1. }
    1. }
    1. catch (Throwable e)
    1. {
    1. e.printStackTrace();
    1. }
    1. finally
    1. {
    1. CloseableUtils.closeQuietly(clientList.get(index));
    1. }
    1. return null;
    1. }
    1. };
    1. service.submit(task);
    1. }
    1. service.shutdown();
    1. service.awaitTermination(10, TimeUnit.MINUTES);
    1. System.out.println("OK!");
    1. }
    1. }
代码也很简单,生成5个client,每个client重复执行10次 请求锁--访问资源--释放锁的过程。每个client都在独立的线程中。
结果可以看到,锁是随机的被每个实例排他性的使用。
既然是可重入锁,你可以在一个线程中多次调用acquire,在线程拥有锁时它总是返回true。
注意:你不应该在多个线程中用同一个InterProcessMutex, 你可以在每个线程中都生成一个InterProcessMutex实例,它们的path都一样,这样它们可以共享同一个锁。
3.示例运行结果
    运行结果控制台如下:
    1. 连接初始化完成!
    1. Client 4 已获取到互斥锁
    1. Client 4 释放互斥锁
    1. Client 3 已获取到互斥锁
    1. Client 3 释放互斥锁
    1. ......
    1. Client 2 已获取到互斥锁
    1. Client 2 释放互斥锁
    1. OK!
    运行时查看Zookeeper节点信息如下:

2.不可重入锁Shared Lock

    这个锁和上面的相比,就是少了Reentrant的功能,也就意味着它不能在同一个线程中重入。这个类是InterProcessSemaphoreMutex使用方法和上面的类类似
    首先我们将上面的例子修改一下,测试一下它的重入。修改ExampleClientThatLocks.doWork,连续两次acquire:
    1. public void doWork(long time, TimeUnit unit) throws Exception
    1. {
    1. if (!lock.acquire(time, unit))
    1. {
    1. throw new IllegalStateException(clientName + " 不能得到互斥锁");
    1. }
    1. System.out.println(clientName + " 已获取到互斥锁");
    1. if (!lock.acquire(time, unit))
    1. {
    1. throw new IllegalStateException(clientName + " 不能得到互斥锁");
    1. }
    1. System.out.println(clientName + " 再次获取到互斥锁");
    1. try
    1. {
    1. resource.use(); // 使用资源
    1. Thread.sleep(1000 * 1);
    1. }
    1. finally
    1. {
    1. System.out.println(clientName + " 释放互斥锁");
    1. lock.release(); // 总是在finally中释放
    1. lock.release(); // 获取锁几次 释放锁也要几次
    1. }
    1. }
注意:我们也需要调用release两次。这和JDK的ReentrantLock用法一致。如果少调用一次release,则此线程依然拥有锁。
上面的代码没有问题,我们可以多次调用acquire,后续的acquire也不会阻塞。
但是将上面的InterProcessMutex换成不可重入锁InterProcessSemaphoreMutex,如果再运行上面的代码,结果就会发现线程被阻塞在第二个acquire上,直到超时。也就是此锁不是可重入的。

3.可重入读写锁Shared Reentrant Read Write Lock

    类似JDK的ReentrantReadWriteLock。一个读写锁管理一对相关的锁。一个负责读操作,另外一个负责写操作。读操作在写锁没被使用时可同时由多个进程使用,而写锁在使用时不允许读(阻塞)。
    此锁是可重入的。一个拥有写锁的线程可重入读锁,但是读锁却不能进入写锁。这也意味着写锁可以降级成读锁, 比如请求写锁 --->读锁 ---->释放写锁。从读锁升级成写锁是不行的。
1.可重入读写锁相关类介绍
    可重入读写锁主要由两个类实现:InterProcessReadWriteLock、InterProcessMutex。使用时首先创建一个InterProcessReadWriteLock实例,然后再根据你的需求得到读锁或者写锁,读写锁的类型是InterProcessMutex。

2.编写示例程序
    示例程序仍使用上面的FakeLimitedResource、InterProcessMutexExample类
    1. public class ExampleClientReadWriteLocks
    1. {
    1. private final InterProcessReadWriteLock lock;
    1. private final InterProcessMutex readLock;
    1. private final InterProcessMutex writeLock;
    1. private final FakeLimitedResource resource;
    1. private final String clientName;
    1. public ExampleClientReadWriteLocks(CuratorFramework client, String lockPath, FakeLimitedResource resource, String clientName)
    1. {
    1. this.resource = resource;
    1. this.clientName = clientName;
    1. lock = new InterProcessReadWriteLock(client, lockPath);
    1. readLock = lock.readLock();
    1. writeLock = lock.writeLock();
    1. }
    1. public void doWork(long time, TimeUnit unit) throws Exception
    1. {
    1. // 注意只能先得到写锁再得到读锁,不能反过来!!!
    1. if (!writeLock.acquire(time, unit))
    1. {
    1. throw new IllegalStateException(clientName + " 不能得到写锁");
    1. }
    1. System.out.println(clientName + " 已得到写锁");
    1. if (!readLock.acquire(time, unit))
    1. {
    1. throw new IllegalStateException(clientName + " 不能得到读锁");
    1. }
    1. System.out.println(clientName + " 已得到读锁");
    1. try
    1. {
    1. resource.use(); // 使用资源
    1. Thread.sleep(1000 * 1);
    1. }
    1. finally
    1. {
    1. System.out.println(clientName + " 释放读写锁");
    1. readLock.release();
    1. writeLock.release();
    1. }
    1. }
    1. }
    在这个类中我们首先请求了一个写锁,然后降级成读锁。执行业务处理,然后释放读写锁。修改InterProcessMutexExample类中的ExampleClientThatLocks为ExampleClientReadWriteLocks然后运行示例。
3.示例运行结果
    运行结果控制台:
    1. 连接初始化完成!
    1. Client 1 已得到写锁
    1. Client 1 已得到读锁
    1. Client 1 释放读写锁
    1. ......
    1. Client 3 已得到写锁
    1. Client 3 已得到读锁
    1. Client 3 释放读写锁
    1. OK!
    此时查看Zookeeper数据节点如下:

4.信号量Shared Semaphore

    一个计数的信号量类似JDK的Semaphore。JDK中Semaphore维护的一组许可(permits),而Cubator中称之为租约(Lease)
    有两种方式可以决定semaphore的最大租约数。第一种方式是有用户给定的path决定。第二种方式使用SharedCountReader类。
    如果不使用SharedCountReader,没有内部代码检查进程是否假定有10个租约而进程B假定有20个租约。 所以所有的实例必须使用相同的numberOfLeases值.
1.信号量实现类说明
主要类有:
  • InterProcessSemaphoreV2 - 信号量实现类
  • Lease - 租约(单个信号)
  • SharedCountReader - 计数器,用于计算最大租约数量
    这次调用acquire会返回一个租约对象。客户端必须在finally中close这些租约对象,否则这些租约会丢失掉。但是,如果客户端session由于某种原因比如crash丢掉,那么这些客户端持有的租约会自动close,这样其它客户端可以继续使用这些租约。
租约还可以通过下面的方式返还:
    1. public void returnLease(Lease lease)
    1. public void returnAll(Collection<Lease> leases)
    注意一次你可以请求多个租约,如果Semaphore当前的租约不够,则请求线程会被阻塞。同时还提供了超时的重载方法。
    1. public Lease acquire() throws Exception
    1. public Collection<Lease> acquire(int qty) throws Exception
    1. public Lease acquire(long time, TimeUnit unit) throws Exception
    1. public Collection<Lease> acquire(int qty, long time, TimeUnit unit) throws Exception
2.编写示例程序
    1. public class InterProcessSemaphoreExample
    1. {
    1. private static final int MAX_LEASE = 10;
    1. private static final String PATH = "/examples/locks";
    1. public static void main(String[] args) throws Exception
    1. {
    1. FakeLimitedResource resource = new FakeLimitedResource();
    1. CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", new ExponentialBackoffRetry(1000, 3));
    1. client.start();
    1. InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, PATH, MAX_LEASE);
    1. Collection<Lease> leases = semaphore.acquire(5);
    1. System.out.println("获取租约数量:" + leases.size());
    1. Lease lease = semaphore.acquire();
    1. System.out.println("获取单个租约");
    1. resource.use();
    1. Collection<Lease> leases2 = semaphore.acquire(5, 10, TimeUnit.SECONDS);
    1. System.out.println("获取租约,如果为空则超时: " + leases2);
    1. System.out.println("释放租约");
    1. semaphore.returnLease(lease);
    1. System.out.println("释放集合中的所有租约");
    1. semaphore.returnAll(leases);
    1. client.close();
    1. System.out.println("OK!");
    1. }
    1. }
首先我们先获得了5个租约,接着请求了一个租约,因为semaphore还有5个租约,所以请求可以满足,返回一个租约,还剩4个租约。
然后再请求一个租约,因为租约不够,阻塞到超时,还是没能满足,返回结果为null。
3.示例运行结果
    运行结果控制台如下:
    1. 获取租约数量:5
    1. 获取单个租约
    1. 获取租约,如果为空则超时: null
    1. 释放租约
    1. 释放集合中的所有租约
    1. OK!
    此时查看Zookeeper数据节点如下:

注意:上面所讲的4种锁都是公平锁(fair)。从ZooKeeper的角度看,每个客户端都按照请求的顺序获得锁。相当公平。

5.多锁对象 Multi Shared Lock

    Multi Shared Lock是一个锁的容器。当调用acquire,所有的锁都会被acquire,如果请求失败,所有的锁都会被release。同样调用release时所有的锁都被release(失败被忽略)。基本上,它就是组锁的代表,在它上面的请求释放操作都会传递给它包含的所有的锁。
1.主要类说明
主要涉及两个类:
  • InterProcessMultiLock - 对所对象实现类
  • InterProcessLock - 分布式锁接口类
它的构造函数需要包含的锁的集合,或者一组ZooKeeper的path。用法和Shared Lock相同。
    1. public InterProcessMultiLock(CuratorFramework client, List<String> paths)
    1. public InterProcessMultiLock(List<InterProcessLock> locks)
2.编写示例程序
    1. public class InterProcessMultiLockExample
    1. {
    1. private static final String PATH1 = "/examples/locks1";
    1. private static final String PATH2 = "/examples/locks2";
    1. public static void main(String[] args) throws Exception
    1. {
    1. FakeLimitedResource resource = new FakeLimitedResource();
    1. CuratorFramework client = CuratorFrameworkFactory.newClient("127.0.0.1:2181", new ExponentialBackoffRetry(1000, 3));
    1. client.start();
    1. InterProcessLock lock1 = new InterProcessMutex(client, PATH1); // 可重入锁
    1. InterProcessLock lock2 = new InterProcessSemaphoreMutex(client, PATH2); // 不可重入锁
    1. InterProcessMultiLock lock = new InterProcessMultiLock(Arrays.asList(lock1, lock2));
    1. if (!lock.acquire(10, TimeUnit.SECONDS))
    1. {
    1. throw new IllegalStateException("不能获取多锁");
    1. }
    1. System.out.println("已获取多锁");
    1. System.out.println("是否有第一个锁: " + lock1.isAcquiredInThisProcess());
    1. System.out.println("是否有第二个锁: " + lock2.isAcquiredInThisProcess());
    1. try
    1. {
    1. resource.use(); // 资源操作
    1. }
    1. finally
    1. {
    1. System.out.println("释放多个锁");
    1. lock.release(); // 释放多锁
    1. }
    1. System.out.println("是否有第一个锁: " + lock1.isAcquiredInThisProcess());
    1. System.out.println("是否有第二个锁: " + lock2.isAcquiredInThisProcess());
    1. client.close();
    1. System.out.println("OK!");
    1. }
    1. }
新建一个InterProcessMultiLock,包含一个重入锁和一个非重入锁。调用acquire后可以看到线程同时拥有了这两个锁。调用release看到这两个锁都被释放了。
注意:再重申一遍,强烈推荐使用ConnectionStateListener监控连接的状态。
3.示例运行结果
    运行结果控制台如下:
    1. 已获取多锁
    1. 是否有第一个锁: true
    1. 是否有第二个锁: true
    1. 释放多个锁
    1. 是否有第一个锁: false
    1. 是否有第二个锁: false
    1. OK!
    此时查看Zookeeper数据节点如下:

-------------------------------------------------------------------------------------------------------------------------------

05.Curator分布式锁的更多相关文章

  1. curator 分布式锁InterProcessMutex

    写这篇文章的目的主要是为了记录下自己在zookeeper 锁上踩过的坑,以及踩坑之后自己的一点认识; 从zk分布式锁原理说起,原理很简单,大家也应该都知道,简单的说就是zookeeper实现分布式锁是 ...

  2. Zookeeper+Curator 分布式锁

    本来想着基于zk临时节点,实现一下分布式锁,结果发现有curator框架.PS:原声API真的难用,连递归创建path都没有? 配置curator maven的时候,md配置了好几个小时,最后发现集中 ...

  3. zookeeper 笔记--curator分布式锁

    使用ZK实现分布式独占锁, 原理就是利用ZK同级节点的唯一性. Curator框架下的一些分布式锁工具InterProcessMutex:分布式可重入排它锁 InterProcessSemaphore ...

  4. 女朋友也能看懂的Zookeeper分布式锁原理

      前言 关于分布式锁,在互联网行业的使用场景还是比较多的,比如电商的库存扣减,秒杀活动,集群定时任务执行等需要进程互斥的场景.而实现分布式锁的手段也很多,大家比较常见的就是redis跟zookeep ...

  5. SpringBoot电商项目实战 — Zookeeper的分布式锁实现

    上一篇演示了基于Redis的Redisson分布式锁实现,那今天我要再来说说基于Zookeeper的分布式现实. Zookeeper分布式锁实现 要用Zookeeper实现分布式锁,我就不得不说说zo ...

  6. 【分布式锁】06-Zookeeper实现分布式锁:可重入锁源码分析

    前言 前面已经讲解了Redis的客户端Redission是怎么实现分布式锁的,大多都深入到源码级别. 在分布式系统中,常见的分布式锁实现方案还有Zookeeper,接下来会深入研究Zookeeper是 ...

  7. Curator Zookeeper分布式锁

    Curator Zookeeper分布式锁 pom.xml中添加如下配置 <!-- https://mvnrepository.com/artifact/org.apache.curator/c ...

  8. zookeeper之分布式锁以及分布式计数器(通过curator框架实现)

    有人可能会问zookeeper我知道,但是curator是什么呢? 其实curator是apachede针对zookeeper开发的一个api框架是apache的顶级项目 他与zookeeper原生a ...

  9. Curator实现分布式锁

    分布式锁的应用 分布式锁服务宕机, ZooKeeper 一般是以集群部署, 如果出现 ZooKeeper 宕机, 那么只要当前正常的服务器超过集群的半数, 依然可以正常提供服务 持有锁资源服务器宕机, ...

随机推荐

  1. php回调函数call_user_func和call_user_func_array详解

    call_user_func($fun); call_user_func 函数类似于一种特别的调用函数的方法,使用方法如下: 1.调用 普通函数: <?php function a($b, $c ...

  2. MySQL防止delete命令删除数据

    在sql中删除数据库中记录我们会使用到delete命令,这样如果不小心给删除了很难恢复了,总结一些删除数据但是不在数据库删除的方法. 方法一 我常用的做法,就是在数据库中加一个删除标识字段,如: is ...

  3. C# FTP操作类可用

    public class FtpClient { #region 构造函数 /// <summary> /// 创建FTP工具 /// <para> /// 默认不使用SSL, ...

  4. CYQ学习主要摘要4

    http://www.cnblogs.com/cyq1162/archive/2010/11/03/1867642.html Xml的处理 http://www.cnblogs.com/cyq1162 ...

  5. 第十六周oj刷题——Problem E: B 构造函数和析构函数

    Description 在建立类对象时系统自己主动该类的构造函数完毕对象的初始化工作, 当类对象生命周期结束时,系统在释放对象空间之前自己主动调用析构函数. 此题要求: 依据主程序(main函数)和程 ...

  6. C数据结构-栈和队列,括号匹配举例---ShinePans

    1.栈和队列是两种特殊的线性表             运算操作被限定仅仅能在表的一端或两端插入,删除元素,故也称它们为限定的线性表结构 2.栈的基本运算 1).Stackinit(&s) 构 ...

  7. 修改JQM的默认配置属性

    从本文开始,使用 jQuery Mobile 与 HTML5 开发 Web App 系列将会进入第三部分——jQuery Mobile 事件与方法,这其中将会利用之前所讲述的 jQuery Mobil ...

  8. Python 数据驱动ddt 使用

    准备工作: pip install ddt 知识点: 一,数据驱动和代码驱动: 数据驱动的意思是  根据你提供的数据来测试的  比如 ATP框架 需要excel里面的测试用例 代码驱动是必须得写代码  ...

  9. Nginx - Windows下Nginx初入门,附CentOS下Nginx的安装

    公司刚使用nginx,预先学习下.鉴于机器没有Linux环境,在Windows熟悉下. 下载 目前(2015-07-11),nginx的稳定版本是1.8.0,在官网下载先,windows版的nginx ...

  10. Hystrix的用法

    package com.example.demo; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; imp ...