Curator分布式锁的实现

       在学习Curator分布式锁的实现之前,建议移步:基于ZooKeeper的分布式锁实现,了解一下ZooKeeper API分布式锁的实现原理。

基于ZooKeeper分布式锁的实现,有两种方式:

        1.利用ZooKeeper同节点路径唯一性(不建议使用)

        2.利用ZooKeeper有序节点特性(临时有序节点)(推荐使用)

Curator分布式锁的实现,就是利用ZooKeeper有序节点特性来实现的。Curator在ZooKeeper中存储的节点名称类似如下:


1.可重入锁和不可重入锁 

        Curator实现了一套的分布式锁,有可重入锁和不可重入锁之分。在Java中,我们知晓的synchronized 和 ReentrantLock,都是可重入锁,那么可重入锁和不可重入锁有什么区别呢,

       可重入锁: 简单理解就是一个类的A、B两个方法,A、B都有获得同一把锁,当A方法调用时,获得锁,在A方法的锁还没有被释放时,调用B方法时,B方法也获得该锁。可重入锁可以防止死锁情况。

       不可重入锁:简单理解就是一个类的A、B两个方法,A、B都有获得统一把锁,当A方法调用时,获得锁,在A方法的锁还没有被释放时,调用B方法时,B方法也获得不了该锁,必须等A方法释放掉这个锁。


2.我们来了解一下Curator分布式锁实现的几个方法

       Curator中,可重入锁的实现,由类InterProcessMutex来实现;不可重入锁的实现,由类InterProcessSemaphoreMutex来实现,使用方法和InterProcessMutex类似。

       我们以InterProcessMutex为例,来了解一下Curator中的一些具体方法的实现。它的构造函数为:

public InterProcessMutex(CuratorFramework client, String path);

通过acquire()方法,可以获得锁,并提供超时机制

public void acquire(){...}
public boolean acquire(long time,TimeUnit uinit){...}

通过release()方法,可以释放锁

public void release(){...}

通过makeRevocable()方法,可以将锁设为可撤销的锁,当别的进程或线程想让你释放锁时,RevocationLister将会回调

public void makeRevocable(RevocationListener listener){...}

如果你要请求撤销当前的锁,可以调用attemptRevoke()方法,注意锁释放时RevocationListener将会回调

public static void attemptRevoke(CuratorFramework client,String path){...}

3.通过情景,使用Curator完成分布式锁的实现 

情景再现:

       现在我们经常会秒杀或者抢购一些商品,为了防止商品卖多卖少的问题出现,我们就会用到锁机制来解决这些问题了。在此处,我们就以抢购手机为例,一共有5台手机,10个人来购买,来使用Curator来完成分布式锁的实现。

       首先我们先创建一个模拟的共享资源Phone类(手机仓库有多少台手机可卖)

public class Phone {
    /**
     * 手机库存数量(5台)
     */
    private int number = 5;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

1.可重入锁的实现

    可重入锁的实现,获得锁后可以满足抢购成功,并完成付款操作。

/**
 * 基于Curator的分布式锁(可重入锁)
 */
public class CuratorDistrutedLock{

    //Curator客户端
    private static CuratorFramework client;
    //名称
    private static String clientName;
    //分布式锁节点(可重入锁)
    private static String lockPath = "/example/lock";
    //集群地址
    private static String clientAddr = "192.168.204.202:2181";

    /**
     * 获取客户端连接(创建节点)
     */
    public static CuratorFramework getClientConnection(){
        //获取Curator连接
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
        client = CuratorFrameworkFactory.newClient(clientAddr, retryPolicy);
        client.start();
        return client;
    }

    /**
     * 可重入锁
     */
    public static void main(String[] args) throws IOException {
        CuratorFramework client = getClientConnection();
        Phone phone = new Phone();
        //可重入锁
        InterProcessMutex mutex = new InterProcessMutex(client,lockPath);
        for (int i = 0; i < 10; i++) {

            new Thread(()->{
                try {
                    //增加第一把锁
                    mutex.acquire();
                    System.out.println(Thread.currentThread().getName() + "获得第一把锁");
                    //完成抢购操作
                    buy(phone);

                    if(phone.getNumber() >= 0){
                        //增加第二把锁
                        mutex.acquire();
                        System.out.println(Thread.currentThread().getName() + "获得第二把锁");
                        //完成付款操作
                        pay();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        //可重入锁,使用几把锁,就释放几把锁
                        //释放第一把锁
                        mutex.release();

                        if(phone.getNumber() >= 0){
                            //释放第二把锁
                            mutex.release();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

    /**
     * 抢购
     */
    public static void buy(Phone phone) {
        System.out.println("【" + Thread.currentThread().getName() + "】开始抢购");
        //获取剩余手机数量

        int currentNumber = phone.getNumber();

        if (currentNumber <= 0) {
            System.out.println("抢购已结束,下次再来吧");
            phone.setNumber(-1);
        } else if(currentNumber > 0){
            System.out.println("剩余手机数量:" + currentNumber);
            //购买后数量减1
            currentNumber--;
            phone.setNumber(currentNumber);
            System.out.println("【" + Thread.currentThread().getName() + "】抢购完成");
        }
        System.out.println("------------------------------------------------------");
    }

    /**
     * 付款
     */
    public static void pay(){
        System.out.println("【" + Thread.currentThread().getName() + "】开始付款");
        System.out.println("【" + Thread.currentThread().getName() + "】付款完成");
        System.out.println("*****************************************************");
    }
}

测试结果:

Thread-6获得第一把锁
【Thread-6】开始抢购
剩余手机数量:5
【Thread-6】抢购完成
------------------------------------------------------
Thread-6获得第二把锁
【Thread-6】开始付款
【Thread-6】付款完成
*****************************************************
Thread-1获得第一把锁
【Thread-1】开始抢购
剩余手机数量:4
【Thread-1】抢购完成
------------------------------------------------------
Thread-1获得第二把锁
【Thread-1】开始付款
【Thread-1】付款完成
*****************************************************
Thread-9获得第一把锁
【Thread-9】开始抢购
剩余手机数量:3
【Thread-9】抢购完成
------------------------------------------------------
Thread-9获得第二把锁
【Thread-9】开始付款
【Thread-9】付款完成
*****************************************************
Thread-5获得第一把锁
【Thread-5】开始抢购
剩余手机数量:2
【Thread-5】抢购完成
------------------------------------------------------
Thread-5获得第二把锁
【Thread-5】开始付款
【Thread-5】付款完成
*****************************************************
Thread-7获得第一把锁
【Thread-7】开始抢购
剩余手机数量:1
【Thread-7】抢购完成
------------------------------------------------------
Thread-7获得第二把锁
【Thread-7】开始付款
【Thread-7】付款完成
*****************************************************
Thread-4获得第一把锁
【Thread-4】开始抢购
抢购已结束,下次再来吧
------------------------------------------------------
Thread-8获得第一把锁
【Thread-8】开始抢购
抢购已结束,下次再来吧
------------------------------------------------------
Thread-10获得第一把锁
【Thread-10】开始抢购
抢购已结束,下次再来吧
------------------------------------------------------
Thread-3获得第一把锁
【Thread-3】开始抢购
抢购已结束,下次再来吧
------------------------------------------------------
Thread-2获得第一把锁
【Thread-2】开始抢购
抢购已结束,下次再来吧
------------------------------------------------------

2.不可重入锁的实现

          不可重入锁这个锁使用InterProcessSemaphoreMutex类来实现,和上面的InterProcessMutex相比,使用操作都一样,就是少了Reentrant的功能,也就意味着它不能在同一个线程中重入。不可重入锁,不能满足获得锁后抢购成功,并付款的操作。不可重入锁,容易造成死锁现象。

        代码方面,将可重入锁部分修改一下即可,其他不变

//可重入锁
InterProcessMutex mutex = new InterProcessMutex(client,lockPath);
//修改为下面这一句话即可
//不可重入锁
InterProcessSemaphoreMutex mutex = new InterProcessSemaphoreMutex(client,lockPath);

测试结果:

Thread-9获得第一把锁
【Thread-9】开始抢购
剩余手机数量:5
【Thread-9】抢购完成
------------------------------------------------------

       使用不可重入锁,你会发现Thread-9(线程9)获得锁后,完成抢购操作后,并没有去执行付款操作,而是停滞在那里,导致其他线程也无法去完成抢购操作,从而造成死锁操作。

       这是因为线程9获得锁后,并没有去释放的原因,这就是不可重入锁和可重入锁的区别所在。如果将不增加第二把锁(同时删除第二把锁的释放操作,该流程便能够顺利执行)


博主写作不易,来个关注呗

求关注、求点赞,加个关注不迷路 ヾ(◍°∇°◍)ノ゙

博主不能保证写的所有知识点都正确,但是能保证纯手敲,错误也请指出,望轻喷 Thanks♪(・ω・)ノ

你可能感兴趣的:(Zookeeper)