原理图
image.png
代码
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
/**
* @author: sjx
* @date: 2020/3/8 09:50
* @description: 基于zk原生api 实现分布式锁
*/
public class DistributedLock implements Lock, Watcher {
private ZooKeeper zooKeeper;
//定义一个根节点
private String ROOT_LOCK = "/locks";
//等待前一个锁
private String WAIT_LOCK;
// 表示当前获得的锁
private String CURRENT_LOCK;
private CountDownLatch countDownLatch;
public DistributedLock() {
try {
zooKeeper = new ZooKeeper("ip:端口",
4000, this);
//判断根节点是否存在
Stat exists = zooKeeper.exists(ROOT_LOCK, false);
if (null == exists) {
//如果不存在,则创建
//创建一个永久节点
zooKeeper.create(ROOT_LOCK, "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void lock() {
if (this.tryLock()) {
//获得锁成功
System.out.println(Thread.currentThread().getName() + "->" + CURRENT_LOCK + "->获得锁成功");
return;
}
waitForLock(WAIT_LOCK);
}
/**
* 阻塞= 等待前一个节点释放锁
*
* @param prev
* @return
*/
private boolean waitForLock(String prev) {
//监听上一个节点是否释放锁 watch =true
try {
//假如此时节点prev= /lock_seq1 ,那么 /lock_seq2 节点则要监听/lock_seq1 是否释放锁
Stat exists = zooKeeper.exists(prev, true);
if (exists != null) {
//如果上个节点还没释放锁
System.out.println(Thread.currentThread().getName() + "->等待锁:" + ROOT_LOCK + "/" + prev + "释放");
countDownLatch = new CountDownLatch(1);
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "->获得锁成功");
}
} catch (KeeperException e) {
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
//创建一个临时有序节点
try {
CURRENT_LOCK = zooKeeper.create(ROOT_LOCK + "/", "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
System.out.println(Thread.currentThread().getName() + "->" + CURRENT_LOCK + ",尝试竞争锁");
//获取根节点下的所有的子节点
List childrens = zooKeeper.getChildren(ROOT_LOCK, false);
//对子节点进行排序
//定义一个集合进行拍讯
SortedSet sortedSet = new TreeSet<>();
for (String children : childrens) {
sortedSet.add(ROOT_LOCK + "/" + children);
}
//获得当前所有子节点中最小的节点
String firstNode = sortedSet.first();
if (CURRENT_LOCK.equals(firstNode)) {
//通过当前节点和最小的节点进行比较如果相等则表示获得锁成功
return true;
}
SortedSet lastSmallNode = sortedSet.headSet(CURRENT_LOCK);
if (!lastSmallNode.isEmpty()) {
//获得比当前节点更小的最后一个节点,设置给WAIT_LOCK
WAIT_LOCK = lastSmallNode.last();
System.out.println("WAIT_LOCK" + WAIT_LOCK);
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) {
return false;
}
/**
* 释放锁
*/
@Override
public void unlock() {
System.out.println(Thread.currentThread().getName() + "->释放锁" + CURRENT_LOCK);
try {
zooKeeper.delete(CURRENT_LOCK, -1);
CURRENT_LOCK = null;
zooKeeper.close();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
@Override
public Condition newCondition() {
return null;
}
/**
* 监听节点的变化
*
* @param watchedEvent
*/
@Override
public void process(WatchedEvent watchedEvent) {
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
}
测试
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
/**
* @author: sjx
* @date: 2020/3/8 11:11
* @description: zk原生分布式锁测试
*/
public class DistributedLockTest {
public static void main(String[] args) throws IOException {
CountDownLatch countDownLatch = new CountDownLatch(5);
//五个线程并发获取锁
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
countDownLatch.await();
DistributedLock distributedLock = new DistributedLock();
//获得锁
distributedLock.lock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "Thread:" + i).start();
countDownLatch.countDown();
}
System.in.read();
}
}
结果
Thread:0->/locks/0000000016,尝试竞争锁
Thread:3->/locks/0000000015,尝试竞争锁
Thread:1->/locks/0000000019,尝试竞争锁
Thread:4->/locks/0000000017,尝试竞争锁
Thread:2->/locks/0000000018,尝试竞争锁
Thread:3->/locks/0000000015->获得锁成功
WAIT_LOCK/locks/0000000018
WAIT_LOCK/locks/0000000016
WAIT_LOCK/locks/0000000015
WAIT_LOCK/locks/0000000017
Thread:1->等待锁:/locks//locks/0000000018释放
Thread:0->等待锁:/locks//locks/0000000015释放
Thread:4->等待锁:/locks//locks/0000000016释放
Thread:2->等待锁:/locks//locks/0000000017释放
五个线程并发获取锁,线程三获得锁,其他线程等待,此时必/locks/0000000015 小的节点是Thread:4(/locks/0000000016) 在终端把/locks/0000000016 这个节点删掉(相当于释放锁),zk的watch 机制会通知thread4 此时Thread3已经释放锁。
[zk: localhost:2181(CONNECTED) 7] ls /locks
[0000000016, 0000000015, 0000000018, 0000000017, 0000000019]
[zk: localhost:2181(CONNECTED) 8] delete /locks/0000000016
[zk: localhost:2181(CONNECTED) 9]
image.png
至此,分布式锁已经完成(没有考虑获取锁的超时时间以及有很多细节没有处理,此demo 只是能更好的理解利用zookeeper 的数据结构和节点的监听机制在分布式锁上运用的原理)。下篇文章介绍使用Curator 自带的分布式锁(原理相同)。