在学习Curator分布式锁的实现之前,建议移步:基于ZooKeeper的分布式锁实现,了解一下ZooKeeper API分布式锁的实现原理。
基于ZooKeeper分布式锁的实现,有两种方式:
1.利用ZooKeeper同节点路径唯一性(不建议使用)
2.利用ZooKeeper有序节点特性(临时有序节点)(推荐使用)
Curator分布式锁的实现,就是利用ZooKeeper有序节点特性来实现的。Curator在ZooKeeper中存储的节点名称类似如下:
Curator实现了一套的分布式锁,有可重入锁和不可重入锁之分。在Java中,我们知晓的synchronized 和 ReentrantLock,都是可重入锁,那么可重入锁和不可重入锁有什么区别呢,
可重入锁: 简单理解就是一个类的A、B两个方法,A、B都有获得同一把锁,当A方法调用时,获得锁,在A方法的锁还没有被释放时,调用B方法时,B方法也获得该锁。可重入锁可以防止死锁情况。
不可重入锁:简单理解就是一个类的A、B两个方法,A、B都有获得统一把锁,当A方法调用时,获得锁,在A方法的锁还没有被释放时,调用B方法时,B方法也获得不了该锁,必须等A方法释放掉这个锁。
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){...}
情景再现:
现在我们经常会秒杀或者抢购一些商品,为了防止商品卖多卖少的问题出现,我们就会用到锁机制来解决这些问题了。在此处,我们就以抢购手机为例,一共有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♪(・ω・)ノ