关于java:郑爽也能看懂的Zookeeper分布式锁原理

36次阅读

共计 13592 个字符,预计需要花费 34 分钟才能阅读完成。

介绍

许多场景中,数据一致性是一个比拟重要的话题,在单机环境中,咱们能够通过 Java 提供的并发 API 来解决;而在分布式环境 (会遇到网络故障、音讯反复、音讯失落等各种问题) 下要简单得多,比方电商的库存扣减,秒杀流动,集群定时工作执行等须要过程互斥的场景。本文次要探讨如何利用 Zookeeper 来实现分布式锁,比照了一些其余计划分布式锁的优缺点。至于应用何种,要因本人的业务场景去决定,没有相对的计划。

分布式锁是什么?

分布式锁是管制分布式系统之间同步访问共享资源的一种形式。

实现分布式锁留神点:
  • 锁的可重入性(递归调用不应该被阻塞、防止死锁)
  • 锁的超时(防止死锁、死循环等意外状况)
  • 锁的阻塞(保障原子性等)
  • 锁的个性反对(阻塞锁、可重入锁、偏心锁、联锁、信号量、读写锁)
应用分布式锁留神点:
  • 分布式锁的开销(分布式锁个别能不必就不必,有些场景能够用乐观锁代替)
  • 加锁的粒度(管制加锁的粒度,能够优化零碎的性能)
  • 加锁的形式

常见的实现分布式锁计划

数据库

基于数据库表惟一索引

最简略的形式就是间接创立一张锁表,当咱们要锁住某个办法或资源时,咱们就在该表中减少一条记录,想要开释锁的时候就删除这条记录。给某字段增加唯一性束缚,如果有多个申请同时提交到数据库的话,数据库会保障只有一个操作能够胜利,那么咱们就能够认为操作胜利的那个线程取得了该办法的锁,能够执行办法体内容。

但会引入数据库单点、无生效工夫、不阻塞、不可重入等问题。

基于数据库排他锁

如果应用的是 MySql 的 InnoDB 引擎,在查问语句前面减少 for update,数据库会在查问过程中 (须通过惟一索引查问) 给数据库表减少排他锁,咱们能够认为取得排它锁的线程即可取得分布式锁,通过 connection.commit() 操作来开释锁。

会引入数据库单点、不可重入、无奈保障肯定应用行锁、排他锁,所以有可能长时间不提交导致占用数据库连贯等问题。

优缺点

  • 长处:

间接借助数据库,容易了解。

  • 毛病

会引入更多的问题,使整个计划变得越来越简单

操作数据库须要肯定的开销,有肯定的性能问题

应用数据库的行级锁并不一定靠谱,尤其是当咱们的锁表并不大的时候

缓存

相比拟于基于数据库实现分布式锁的计划来说,基于缓存来实现在性能方面会体现的更好一点,目前有很多成熟的缓存产品,包含 Redis、memcached、tair 等。

基于 redis 的 setnx()、expire() 办法做分布式锁

setnx 的含意就是 SET if Not Exists,其次要有两个参数 setnx(key, value)。该办法是原子的,如果 key 不存在,则设置以后 key 胜利,返回 1;如果以后 key 曾经存在,则设置以后 key 失败,返回 0。

expire 设置过期工夫,要留神的是 setnx 命令不能设置 key 的超时工夫,只能通过 expire() 来对 key 设置。

redis 有个命令能够实现 setnx 和 expire 同样成果的原子性指令,博主这边解释并未提及到。命令:
set k1 v1 ex 10 nx。当然为了实现以上博主说的两个命令原子性操作,也能够应用 lua 脚本实现。

基于 Redlock 做分布式锁

Redlock 是 Redis 的作者 antirez 给出集群模式的 Redis 分布式锁,它基于 N 个齐全独立的 Redis 节点(通常状况下 N 能够设置成 5)

基于 redisson 做分布式锁

redisson 是 redis 官网的分布式锁组件

优缺点

  • 长处

性能好

  • 毛病

实现中须要思考的因素太多,
通过超时工夫来管制锁的生效工夫并不是非常的靠谱

Zookeeper

大抵思维

每个客户端对某个办法加锁时,在 Zookeeper 上与该办法对应的指定节点目录下,生成一个惟一的长期有序节点(zk 有主动生成有序节点的性能)。判断是否获取锁的形式很简略,只须要判断有序节点中序号最小的一个。当开释锁的时候,只需将这个长期节点删除即可。同时,其能够防止服务宕机导致的锁无奈开释,而产生的死锁问题

Zookeeper 实现分布式锁的外围原理

1. 排他锁

排他锁,又称写锁或独占锁。如果事务 T1 对数据对象 O1 加上了排他锁,那么在整个加锁期间,只容许事务 T1 对 O1 进行读取或更新操作,其余事务都不能对这个数据对象进行任何操作,直到 T1 开释了排他锁。

排他锁外围是保障以后有且仅有一个事务取得锁,并且锁开释之后,所有正在期待获取锁的事务都可能被告诉到。

Zookeeper 的强一致性个性,可能很好地保障在分布式高并发状况下,创立节点可能保障全局唯一性,能够利用 Zookeeper 这个个性,实现排他锁。

就是读写互斥、写写互斥、读读互斥

实现原理,有 3 个外围步骤:定义锁、获取锁、开释锁

  • 定义锁

通过 Zookeeper 上的数据节点来示意一个锁

  • 获取锁

客户端通过调用 create 办法创立示意锁的长期节点。创立胜利,认为客户端取得锁。创立失败,认为锁被占用。同时让没有取得锁的节点在该节点上注册 Watcher 监听,以便实时监听到 lock 节点的变更状况,再次去获取锁。

  • 开释锁

以后取得锁的客户端产生宕机或异样,那么 Zookeeper 上这个长期节点就会被删除,锁也就开释了。

失常执行完业务逻辑,客户端被动删除本人创立的长期节点。

2. 共享锁

共享锁,又称读锁。如果事务 T1 对数据对象 O1 加上了共享锁,那么以后事务只能对 O1 进行读取操作,其余事务也只能对这个数据对象加共享锁,直到该数据对象上的所有共享锁都被开释。

共享锁与排他锁的区别在于,加了排他锁之后,数据对象只对以后事务可见,而加了共享锁之后,数据对象对所有事务都可见。

总结,读读共享,读写互斥。都是读申请不锁资源,资源共享。有读有写就锁资源。

实现原理也是 3 个外围步骤:

  • 定义锁

通过 Zookeeper 上的数据节点来示意一个锁,是一个相似于 /lockpath/[hostname]- 申请类型 - 序号 的长期程序节点

  • 获取锁

客户端通过调用 create 办法创立示意锁的长期程序节点。如果是读申请,则创立 /lockpath/[hostname]-R- 序号 节点,如果是写申请则创立 /lockpath/[hostname]-W- 序号 节点

判断取得共享锁的逻辑为:

  1. 创立完节点后,获取 /lockpath 节点下的所有子节点,并对该节点注册子节点变更的 Watcher 监听
  2. 确定本人的节点序号
  3. 对于读申请,如果比发现有比本人序号小的写申请就期待持续获取锁,没有则获取共享锁,操作资源。对于写申请,如果发现有比本人序号小的读 / 写申请就期待获取锁,没有则获取锁。
  4. 接管到 Watcher 告诉后,反复步骤 1
  • 开释锁

与排他锁逻辑统一。

3. 羊群效应的产生

在实现共享锁的“判断读写程序”的第 1 个步骤是:创立完节点后,获取 /lockpath 节点下的所有子节点,并对该节点注册子节点变更的 Watcher 监听。这样的话,任何一次客户端移除共享锁之后,Zookeeper 将会发送子节点变更的 Watcher 告诉给所有机器,零碎中将有大量的“Watcher 告诉”和“子节点列表获取”这个操作反复执行,而后所有节点再判断本人是否是序号最小的节点 (写申请) 或者判断比本人序号小的子节点是否都是读申请(读申请),从而持续期待下一次告诉。

然而,这些反复操作很多都是“无用的”,实际上每个锁竞争者只须要关注序号比本人小的那个节点是否存在即可。

当集群规模比拟大时,这些“无用的”操作不仅会对 Zookeeper 造成微小的性能影响和网络冲击,更为严重的是,如果同一时间有多个客户端开释了共享锁,Zookeeper 服务器就会在短时间外向其余客户端发送大量的事件告诉–这就是所谓的“羊群效应“。

改良后的分布式锁实现:

  1. 客户端调用 create 办法创立一个相似于 /lockpath/[hostname]- 申请类型 - 序号 的长期程序节点
  2. 客户端调用 getChildren 办法获取所有曾经创立的子节点列表(这里不注册任何 Watcher)
  3. 读申请:向比本人序号小的最初一个写申请节点注册 Watcher 监听,写申请:向比本人序号小的最初一个节点注册 Watcher 监听(有无获取锁的逻辑同上)
  4. 期待 Watcher 监听,持续进入步骤 2

用 Curator 客户端实现分布式锁

Apache Curator 是一个 Zookeeper 的开源客户端,它提供了 Zookeeper 各种利用场景(Recipe,如共享锁服务、master 选举、分布式计数器等)的形象封装,接下来将利用 Curator 提供的类来实现分布式锁,Curator 提供的跟分布式锁相干的类有 5 个,别离是:

  1. Shared Reentrant Lock 可重入锁
  2. Shared Lock 共享不可重入锁
  3. Shared Reentrant Read Write Lock 可重入读写锁
  4. Shared Semaphore 信号量
  5. Multi Shared Lock 多锁

对于错误处理:还是强烈推荐应用 ConnectionStateListener 解决连贯状态的扭转。当连贯 LOST 时你不再领有锁。

1. 可重入锁

Shared Reentrant Lock,全局可重入锁,所有客户端都能够申请,同一个客户端在领有锁的同时,能够屡次获取,不会被阻塞。它是由类 InterProcessMutex 来实现,它的次要办法:

// 构造方法
public InterProcessMutex(CuratorFramework client, String path)
public InterProcessMutex(CuratorFramework client, String path, LockInternalsDriver driver)
// 通过 acquire 取得锁, 并提供超时机制:public void acquire() throws Exception
public boolean acquire(long time, TimeUnit unit) throws Exception
// 撤销锁
public void makeRevocable(RevocationListener<InterProcessMutex> listener)
public void makeRevocable(final RevocationListener<InterProcessMutex> listener, Executor executor)

定义一个 FakeLimitedResource 类来模仿共享资源,该资源一次只能被一个线程应用,直到应用完结,下一个线程能力应用,否则会抛出异样。

public class FakeLimitedResource {private final AtomicBoolean inUse = new AtomicBoolean(false);

    // 模仿只能单线程操作的资源
    public void use() throws InterruptedException {if (!inUse.compareAndSet(false, true)) {
            // 在正确应用锁的状况下,此异样不可能抛出
            throw new IllegalStateException("Needs to be used by one client at a time");
        }
        try {Thread.sleep((long) (100 * Math.random()));
        } finally {inUse.set(false);
        }
    }
}

上面的代码将创立 N 个线程来模仿分布式系统中的节点,零碎将通过 InterProcessMutex 来管制对资源的同步应用。

每个节点都将发动 10 次申请,实现 申请锁 – 拜访资源 – 再次申请锁 – 开释锁 – 开释锁 的过程。

客户端通过 acquire 申请锁,通过 release 开释锁,取得几把锁就要开释几把锁。

这个共享资源一次只能被一个线程应用,如果管制同步失败,将抛异样。

public class SharedReentrantLockTest {
    private static final String lockPath = "/testZK/sharedreentrantlock";
    private static final Integer clientNums = 5;
    final static FakeLimitedResource resource = new FakeLimitedResource(); // 共享的资源
    private static CountDownLatch countDownLatch = new CountDownLatch(clientNums);

    public static void main(String[] args) throws InterruptedException {for (int i = 0; i < clientNums; i++) {
            String clientName = "client#" + i;
            new Thread(new Runnable() {
                @Override
                public void run() {CuratorFramework client = ZKUtils.getClient();
                    client.start();
                    Random random = new Random();
                    try {final InterProcessMutex lock = new InterProcessMutex(client, lockPath);
                        // 每个客户端申请 10 次共享资源
                        for (int j = 0; j < 10; j++) {if (!lock.acquire(10, TimeUnit.SECONDS)) {throw new IllegalStateException(j + "." + clientName + "不能失去互斥锁");
                            }
                            try {System.out.println(j + "." + clientName + "已获取到互斥锁");
                                resource.use(); // 应用资源
                                if (!lock.acquire(10, TimeUnit.SECONDS)) {throw new IllegalStateException(j + "." + clientName + "不能再次失去互斥锁");
                                }
                                System.out.println(j + "." + clientName + "已再次获取到互斥锁");
                                lock.release(); // 申请几次锁就要开释几次锁} finally {System.out.println(j + "." + clientName + "开释互斥锁");
                                lock.release(); // 总是在 finally 中开释}
                            Thread.sleep(random.nextInt(100));
                        }
                    } catch (Throwable e) {System.out.println(e.getMessage());
                    } finally {CloseableUtils.closeQuietly(client);
                        System.out.println(clientName + "客户端敞开!");
                        countDownLatch.countDown();}
                }
            }).start();}
        countDownLatch.await();
        System.out.println("完结!");
    }
}

控制台打印日志,能够看到对资源的同步访问控制胜利,并且锁是可重入的

0. client#3 已获取到互斥锁
0. client#3 已再次获取到互斥锁
0. client#3 开释互斥锁
0. client#1 已获取到互斥锁
0. client#1 已再次获取到互斥锁
0. client#1 开释互斥锁
0. client#2 已获取到互斥锁
0. client#2 已再次获取到互斥锁
0. client#2 开释互斥锁
0. client#0 已获取到互斥锁
0. client#0 已再次获取到互斥锁
0. client#0 开释互斥锁
0. client#4 已获取到互斥锁
0. client#4 已再次获取到互斥锁
0. client#4 开释互斥锁
1. client#1 已获取到互斥锁
1. client#1 已再次获取到互斥锁
1. client#1 开释互斥锁
2. client#1 已获取到互斥锁
2. client#1 已再次获取到互斥锁
2. client#1 开释互斥锁
1. client#4 已获取到互斥锁
1. client#4 已再次获取到互斥锁
1. client#4 开释互斥锁
1. client#3 已获取到互斥锁
1. client#3 已再次获取到互斥锁
1. client#3 开释互斥锁
1. client#2 已获取到互斥锁
1. client#2 已再次获取到互斥锁
1. client#2 开释互斥锁
2. client#4 已获取到互斥锁
2. client#4 已再次获取到互斥锁
2. client#4 开释互斥锁
....
....
client#2 客户端敞开!9. client#0 已获取到互斥锁
9. client#0 已再次获取到互斥锁
9. client#0 开释互斥锁
9. client#3 已获取到互斥锁
9. client#3 已再次获取到互斥锁
9. client#3 开释互斥锁
client#0 客户端敞开!8. client#4 已获取到互斥锁
8. client#4 已再次获取到互斥锁
8. client#4 开释互斥锁
9. client#4 已获取到互斥锁
9. client#4 已再次获取到互斥锁
9. client#4 开释互斥锁
client#3 客户端敞开!client#4 客户端敞开!完结!

同时在程序运行期间查看 Zookeeper 节点树,能够发现每一次申请的锁实际上对应一个长期程序节点

[zk: localhost:2181(CONNECTED) 42] ls /testZK/sharedreentrantlock
[leases, _c_208d461b-716d-43ea-ac94-1d2be1206db3-lock-0000001659, locks, _c_64b19dba-3efa-46a6-9344-19a52e9e424f-lock-0000001658, _c_cee02916-d7d5-4186-8867-f921210b8815-lock-0000001657]
2. 不可重入锁

Shared Lock 与 Shared Reentrant Lock 类似,然而不可重入,这个不可重入锁由类 InterProcessSemaphoreMutex 来实现,应用办法和下面的相似。

将下面程序中的 InterProcessMutex 换成不可重入锁 InterProcessSemaphoreMutex,如果再运行下面的代码,后果就会发现线程被阻塞在第二个 acquire 上,直到超时,也就是此锁不是可重入的。控制台输入日志如下:

0. client#2 已获取到互斥锁
0. client#1 不能失去互斥锁
0. client#4 不能失去互斥锁
0. client#0 不能失去互斥锁
0. client#3 不能失去互斥锁
client#1 客户端敞开!client#4 客户端敞开!client#3 客户端敞开!client#0 客户端敞开!0. client#2 开释互斥锁
0. client#2 不能再次失去互斥锁
client#2 客户端敞开!完结!

把第二个获取锁的代码正文,程序能力失常执行

0. client#1 已获取到互斥锁
0. client#1 开释互斥锁
0. client#2 已获取到互斥锁
0. client#2 开释互斥锁
0. client#0 已获取到互斥锁
0. client#0 开释互斥锁
0. client#4 已获取到互斥锁
0. client#4 开释互斥锁
0. client#3 已获取到互斥锁
0. client#3 开释互斥锁
1. client#1 已获取到互斥锁
1. client#1 开释互斥锁
1. client#2 已获取到互斥锁
1. client#2 开释互斥锁
....
....
9. client#4 已获取到互斥锁
9. client#4 开释互斥锁
9. client#0 已获取到互斥锁
client#2 客户端敞开!9. client#0 开释互斥锁
9. client#1 已获取到互斥锁
client#0 客户端敞开!client#4 客户端敞开!9. client#1 开释互斥锁
9. client#3 已获取到互斥锁
client#1 客户端敞开!9. client#3 开释互斥锁
client#3 客户端敞开!完结!
3. 可重入读写锁

Shared Reentrant Read Write Lock,可重入读写锁,一个读写锁治理一对相干锁,一个负责读操作,另外一个负责写操作;读操作在写锁没被应用时可同时由多个过程应用,而写锁在应用时不容许读(阻塞);此锁是可重入的;一个领有写锁的线程可重入读锁,然而读锁却不能进入写锁,这也意味着写锁能够降级成读锁,比方 申请写锁 —> 读锁 —-> 开释写锁;从读锁升级成写锁是不行的。

可重入读写锁次要由两个类实现:InterProcessReadWriteLock、InterProcessMutex,应用时首先创立一个 InterProcessReadWriteLock 实例,而后再依据你的需要失去读锁或者写锁,读写锁的类型是 InterProcessMutex。

能够了解为咱们下面剖析的共享锁

 public static void main(String[] args) throws InterruptedException {for (int i = 0; i < clientNums; i++) {
            final String clientName = "client#" + i;
            new Thread(new Runnable() {
                @Override
                public void run() {CuratorFramework client = ZKUtils.getClient();
                    client.start();
                    final InterProcessReadWriteLock lock = new InterProcessReadWriteLock(client, lockPath);
                    final InterProcessMutex readLock = lock.readLock();
                    final InterProcessMutex writeLock = lock.writeLock();

                    try {
                        // 留神只能先失去写锁再失去读锁,不能反过来!!!if (!writeLock.acquire(10, TimeUnit.SECONDS)) {throw new IllegalStateException(clientName + "不能失去写锁");
                        }
                        System.out.println(clientName + "已失去写锁");
                        if (!readLock.acquire(10, TimeUnit.SECONDS)) {throw new IllegalStateException(clientName + "不能失去读锁");
                        }
                        System.out.println(clientName + "已失去读锁");
                        try {resource.use(); // 应用资源
                        } finally {System.out.println(clientName + "开释读写锁");
                            readLock.release();
                            writeLock.release();}
                    } catch (Exception e) {System.out.println(e.getMessage());
                    } finally {CloseableUtils.closeQuietly(client);
                        countDownLatch.countDown();}
                }
            }).start();}
        countDownLatch.await();
        System.out.println("完结!");
    }
}

控制台打印日志

client#1 已失去写锁
client#1 已失去读锁
client#1 开释读写锁
client#2 已失去写锁
client#2 已失去读锁
client#2 开释读写锁
client#0 已失去写锁
client#0 已失去读锁
client#0 开释读写锁
client#4 已失去写锁
client#4 已失去读锁
client#4 开释读写锁
client#3 已失去写锁
client#3 已失去读锁
client#3 开释读写锁
完结!
4. 信号量

Shared Semaphore,一个计数的信号量相似 JDK 的 Semaphore,JDK 中 Semaphore 保护的一组许可(permits),而 Cubator 中称之为租约(Lease)。

有两种形式能够决定 semaphore 的最大租约数,在构造方法初始化的时候实现。第一种形式是由用户给定 maxLeases 决定,第二种形式应用 SharedCountReader 类。

  public InterProcessSemaphoreV2(CuratorFramework client, String path, int maxLeases)
  public InterProcessSemaphoreV2(CuratorFramework client, String path, SharedCountReader count)

信号量次要实现类有

InterProcessSemaphoreV2 - 信号量实现类
Lease - 租约(单个信号)
SharedCountReader - 计数器,用于计算最大租约数量

你能够申请多个租约,通过调用 acquire 办法实现。如果 Semaphore 以后的租约不够,则申请线程会被阻塞,同时还提供了超时的重载办法。

public Lease acquire() throws Exception
public Collection<Lease> acquire(int qty) throws Exception
public Lease acquire(long time, TimeUnit unit) throws Exception
public Collection<Lease> acquire(int qty, long time, TimeUnit unit) throws Exception

调用 acquire 会返回一个租约对象,客户端必须在 finally 中 close 这些租约对象,否则这些租约会失落掉。然而,如果客户端 session 因为某种原因比方 crash 丢掉,那么这些客户端持有的租约会主动 close,这样其它客户端能够持续应用这些租约。租约还能够通过上面的几个办法返还,能够返回一个,也能够返回多个。

public void returnLease(Lease lease)
public void returnAll(Collection<Lease> leases) 

一个 Demo 程序如下

public class SharedSemaphoreTest {
    private static final int MAX_LEASE = 10;
    private static final String PATH = "/testZK/semaphore";
    private static final FakeLimitedResource resource = new FakeLimitedResource();

    public static void main(String[] args) throws Exception {CuratorFramework client = ZKUtils.getClient();
        client.start();
        InterProcessSemaphoreV2 semaphore = new InterProcessSemaphoreV2(client, PATH, MAX_LEASE);
        Collection<Lease> leases = semaphore.acquire(5);
        System.out.println("获取租约数量:" + leases.size());
        Lease lease = semaphore.acquire();
        System.out.println("获取单个租约");
        resource.use(); // 应用资源
        // 再次申请获取 5 个 leases,此时 leases 数量只剩 4 个,不够,将超时
        Collection<Lease> leases2 = semaphore.acquire(5, 10, TimeUnit.SECONDS);
        System.out.println("获取租约,如果超时将为 null:" + leases2);
        System.out.println("开释租约");
        semaphore.returnLease(lease);
        // 再次申请获取 5 个,这次刚好够
        leases2 = semaphore.acquire(5, 10, TimeUnit.SECONDS);
        System.out.println("获取租约,如果超时将为 null:" + leases2);
        System.out.println("开释汇合中的所有租约");
        semaphore.returnAll(leases);
        semaphore.returnAll(leases2);
        client.close();
        System.out.println("完结!");
    }
}

控制台打印日志

获取租约数量:5
获取单个租约
获取租约,如果超时将为 null:null
开释租约
获取租约,如果超时将为 null:[org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2$3@3108bc, org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2$3@370736d9, org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2$3@5f9d02cb, org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2$3@63753b6d, org.apache.curator.framework.recipes.locks.InterProcessSemaphoreV2$3@6b09bb57]
开释汇合中的所有租约
完结!

下面所讲的 4 种锁都是偏心锁(fair)。从 ZooKeeper 的角度看,每个客户端都依照申请的程序取得锁,相当偏心。

5. 多锁

Multi Shared Lock 是一个锁的容器。当调用 acquire,所有的锁都会被 acquire,如果申请失败,所有的锁都会被 release。同样调用 release 时所有的锁都被 release(失败被疏忽)。基本上,它就是组锁的代表,在它下面的申请开释操作都会传递给它蕴含的所有的锁。

次要波及两个类:

InterProcessMultiLock - 对所对象实现类
InterProcessLock - 分布式锁接口类

它的构造函数须要蕴含锁的汇合,或者一组 ZooKeeper 的 path,用法和 Shared Lock 相

public InterProcessMultiLock(CuratorFramework client, List<String> paths)
public InterProcessMultiLock(List<InterProcessLock> locks)

一个 Demo 程序如下

public class MultiSharedLockTest {
    private static final String lockPath1 = "/testZK/MSLock1";
    private static final String lockPath2 = "/testZK/MSLock2";
    private static final FakeLimitedResource resource = new FakeLimitedResource();

    public static void main(String[] args) throws Exception {CuratorFramework client = ZKUtils.getClient();
        client.start();

        InterProcessLock lock1 = new InterProcessMutex(client, lockPath1); // 可重入锁
        InterProcessLock lock2 = new InterProcessSemaphoreMutex(client, lockPath2); // 不可重入锁
        // 组锁,多锁
        InterProcessMultiLock lock = new InterProcessMultiLock(Arrays.asList(lock1, lock2));
        if (!lock.acquire(10, TimeUnit.SECONDS)) {throw new IllegalStateException("不能获取多锁");
        }
        System.out.println("已获取多锁");
        System.out.println("是否有第一个锁:" + lock1.isAcquiredInThisProcess());
        System.out.println("是否有第二个锁:" + lock2.isAcquiredInThisProcess());
        try {resource.use(); // 资源操作
        } finally {System.out.println("开释多个锁");
            lock.release(); // 开释多锁}
        System.out.println("是否有第一个锁:" + lock1.isAcquiredInThisProcess());
        System.out.println("是否有第二个锁:" + lock2.isAcquiredInThisProcess());
        client.close();
        System.out.println("完结!");
    }
}

读完是否有疑难?无妨留个言一起探讨,探讨!!整顿自互联网,未知出处,文章略有改变。如有侵权请及时分割哦!

正文完
 0