zk分布式锁的两种实现
ZkLockService :定义一个公共接口
/**
* zk分布式锁公共接口
*/
public interface ZkLockService {
/**
* 加锁接口
*
* @param path 抢锁路径
* @param flag 抢锁线程自己的标志
* @return
*/
Boolean lock(String path, String flag);
/**
* 解锁接口
*
* @param path 抢锁路径
* @param flag 抢锁线程自己的标志
* @return
*/
Boolean unlock(String path, String flag);
/**
* 判断当前节点是否已被抢 true表示被占有了
*
* @param path 节点路径
* @return
*/
Boolean exists(String path);
}
都去抢注册一个节点
这种方式可以将同步做成非阻塞式的,就是未获取到锁,就返回。当然也可以做成阻塞式。
NonBlockZk :实现公共接口。
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 非阻塞:抢占注册同一个节点
*/
public class NonBlockZk implements ZkLockService {
private ZooKeeper zkClient;
/**
* 保证zk能脸上服务
*/
private CountDownLatch countDownLatch = new CountDownLatch(1);
/**
* 测试用,用来测试累加
*/
public static int TEST_NO1 = 0;
/**
* 各线程抢占的节点
*/
public static String LOCK_PATH = "/lock";
{
init();
}
public void init(){
try {
zkClient = new ZooKeeper("127.0.0.1:2181", 5000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
countDownLatch.countDown();
}
});
countDownLatch.await();
} catch (Exception e) {
e.printStackTrace();
}
}
public String getId() {
return UUID.randomUUID().toString();
}
@Override
public Boolean lock(String path, String flag) {
try {
if (!exists(path)) {
//加锁失败,这里会抛异常
zkClient.create(path, flag.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
byte[] data = zkClient.getData(path, false, null);
//判断是不是自己线程打的标记
if (data != null && flag.equals(new String(data))) {
System.out.println(Thread.currentThread().getName() + "获取成功");
return true;
}
}
} catch (KeeperException e) {
return false;
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
//这个flag的作用是,判断是不是当前线程删除的
@Override
public Boolean unlock(String path, String flag) {
try {
Stat stat = new Stat();
byte[] data = zkClient.getData(path, false, stat);
//当前线程释放锁时,判断是不是该线程打的标记
if (data != null && flag.equals(new String(data))) {
zkClient.delete(path, stat.getVersion());
System.out.println(Thread.currentThread().getName() + "释放了锁");
return true;
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public Boolean exists(String path) {
Stat exists = null;
try {
exists = zkClient.exists(path, false);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return exists != null;
}
public Boolean close() {
try {
zkClient.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
import com.lq.fitness.zk.yuansheng.yongjiu.NonBlockZk;
/**
* 定义一个测试业务类
*/
public class YeWu implements Runnable {
private NonBlockZk lock = new NonBlockZk();
@Override
public void run() {
//给当前线程生成一个id
String id = lock.getId();
try {
//如果是if,没获取锁就直接退出,while表示直到获取锁为止
while (!lock.lock(NonBlockZk.LOCK_PATH, id)) {
/*System.out.println("获取锁失败:" + Thread.currentThread().getName());
return;*/
}
NonBlockZk.TEST_NO1++;
NonBlockZk.TEST_NO1++;
NonBlockZk.TEST_NO1++;
NonBlockZk.TEST_NO1++;
NonBlockZk.TEST_NO1++;
NonBlockZk.TEST_NO1++;
NonBlockZk.TEST_NO1++;
NonBlockZk.TEST_NO1++;
NonBlockZk.TEST_NO1++;
NonBlockZk.TEST_NO1++;
System.out.println(NonBlockZk.TEST_NO1);
} finally {
//解锁
lock.unlock(NonBlockZk.LOCK_PATH, id);
lock.close();
}
}
}
/**
* 启动测试
*/
public class Test {
public static void main(String[] args) {
//启动100个线程去对 NonBlockZk.TEST_NO 做++
for (int i = 0; i < 10000; i++) {
new Thread(new YeWu()).start();
}
}
}
基于在同一个路径下注册带序号的节点
ZkLock
import com.alibaba.druid.util.StringUtils;
import com.lq.fitness.zk.yuansheng.yongjiu.ZkLockService;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* zk分布式锁
*/
public class ZkLock implements ZkLockService {
/**
* 测试用,用来测试累加
*/
public static int TEST_NO1 = 0;
private ZooKeeper zkclient = null;
/**
* 所有锁都会注册在这个路径下
*/
private String rootPath = "/seqLock";
/**
* 注册节点的全路径,-符号后面会自动加上序号
*/
private String rootSeqPath = rootPath + "/seq-";
/**
* 保证zk能连接上,zk连接上后才释放
*/
private CountDownLatch zkInitLatch = new CountDownLatch(1);
/**
* 这个对象是用来等待获取锁,获取锁后才会释放
*/
private CountDownLatch lockLatch = new CountDownLatch(1);
/**
* 存放前一个节点
*/
private String prePath;
/**
* 当前节点
*/
private String myPath;
/**
* 获取锁是否成功
*/
private Boolean lockRes = Boolean.FALSE;
{
init();
}
public void init() {
try {
zkclient = new ZooKeeper("127.0.0.1:2181", 15000, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
//如果客户端连接上服务端,就释放等待zkInitLatch
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
zkInitLatch.countDown();
}
//如果监听的事件为NodeDeleted并且被删除的节点为前一个路径节点prePath
if (watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(prePath)) {
lockLatch.countDown();
lockRes = true;
}
}
});
zkInitLatch.await();
exists(rootPath);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Boolean lock(String path, String flag) {
//创建对应的临时节点带序号
try {
myPath = zkclient.create(rootSeqPath, rootSeqPath.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
if (StringUtils.isEmpty(myPath)) {
throw new RuntimeException("异常");
}
List<String> children = zkclient.getChildren(rootPath, false);
if (children == null || children.isEmpty()) {
throw new RuntimeException("异常");
}
if (children.size() == 1) {
//表示可以直接加锁
System.out.println(Thread.currentThread().getName()+"获取了锁" + myPath);
return true;
}else {
Collections.sort(children);
System.out.println(Thread.currentThread().getName()+"加锁时还有这么多节点:" + children + "我的节点:" + myPath);
String myPathSeq = myPath.substring(myPath.lastIndexOf("/") + 1);
int myIndex = children.indexOf(myPathSeq);
if (myIndex == 0) {
return true;
}
prePath = rootPath + "/" +children.get(myIndex-1);
try {
zkclient.getData(prePath, true, null);
System.out.println(Thread.currentThread().getName()+"监听了:" + prePath );
} catch (KeeperException.NoNodeException e) {
//表示前面那个节点已经不存在了,可以直接上锁
return true;
}
//等待获取锁,如果获取了,会在watch函数中解除等待
lockLatch.await();
}
} catch (KeeperException e) {
e.printStackTrace();
return lockRes;
} catch (InterruptedException e) {
e.printStackTrace();
return lockRes;
}
if (lockRes) {
System.out.println(Thread.currentThread().getName()+"获取了锁" + myPath);
}
return lockRes;
}
@Override
public Boolean unlock(String path, String flag) {
//删除当前序号节点
try {
System.out.println(Thread.currentThread().getName()+"释放锁:" + myPath);
zkclient.delete(myPath, -1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
return true;
}
@Override
public Boolean exists(String path) {
Stat exists = null;
try {
exists = zkclient.exists(path, false);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
if (exists == null) {
try {
zkclient.create(rootPath, rootPath.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return true;
}
public Boolean close() {
try {
zkclient.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
import com.lq.fitness.zk.yuansheng.linshi.ZkLock;
/**
* 定义一个测试业务类
*/
public class YeWu implements Runnable {
private ZkLock lock = new ZkLock();
@Override
public void run() {
//给当前线程生成一个id
try {
//如果是if,没获取锁就直接退出,while表示直到获取锁为止
if (!lock.lock(null, null)) {
System.out.println("获取锁失败:"+Thread.currentThread().getName());
return;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.TEST_NO1++;
lock.TEST_NO1++;
lock.TEST_NO1++;
lock.TEST_NO1++;
lock.TEST_NO1++;
lock.TEST_NO1++;
lock.TEST_NO1++;
lock.TEST_NO1++;
lock.TEST_NO1++;
lock.TEST_NO1++;
System.out.println(ZkLock.TEST_NO1);
} finally {
//解锁
lock.unlock(null, null);
lock.close();
}
}
}
/**
* 启动测试
*/
public class Test {
public static void main(String[] args) {
//启动100个线程去对 NonBlockZk.TEST_NO 做++
for (int i = 0; i < 200; i++) {
new Thread(new YeWu()).start();
}
}
}