zookeeper lock自定义锁的死锁问题
OrderService
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
private Lock lock = new ZookeeperDistrbuteLock();
// @Override
public void run() {
getNumber();
}
public void getNumber() {
lock.getlock();
// synchronized (OrderService.class){
String number = orderNumGenerator.getNumber();
// System.out.println(Thread.currentThread().getName() + ",生成订单ID:" + number);
// }
lock.unlock();
}
//
public static void main(String[] args) {
OrderService orderService = new OrderService();
System.out.println("####生成唯一订单号###");
for (int i = 0; i < 100; i++) {
new Thread(new OrderService()).start();
// new Thread(orderService).start();
}
}
}
Lock
public interface Lock {
void getlock();//上锁
void unlock();//解锁
}
ZookeeperAbstractLock
public abstract class ZookeeperAbstractLock implements Lock {
// zk连接地址
private static final String CONNECTSTRING = "127.0.0.1:2181";
// 创建zk连接对象 protect是因为子类要用
protected ZkClient zkClient = new ZkClient(CONNECTSTRING);
//所有线程共同去争取的资源
protected static final String PATH = "/lock";
@Override
public void getlock() {
//线程尝试去创建path节点,(获得权力)
//返回值boolean
//true 抢到资源 正常执行
//false 没抢到资源 插入节点的时候节点已存在==>报错
//之后执行等待waitlock 线程等待,开启监听path节点被释放,唤醒线程继续执行
//递归接着抢
if(trylock()){
// System.out.println("##获取lock锁的资源####");
}else{
// System.out.println(Thread.currentThread().getName()+"在等待资源");
waitlock();
getlock();
}
}
abstract boolean trylock();
abstract void waitlock() ;
@Override
public void unlock() {
//关闭zookeeper连接
if(zkClient!=null){
zkClient.close();
// System.out.println("释放锁资源。。。。");
}
}
}
ZookeeperDistrbuteLock
public class ZookeeperDistrbuteLock extends ZookeeperAbstractLock {
//每个线程是不是都需要自己的信号量
private CountDownLatch countDownLatch =null;
@Override
boolean trylock() {
//就是创建path的临时节点
//为什么选择使用临时节点?
//当你连接断开时(正常断开和意外断开),该连接创建的临时节点都会消失。
//创建节点(多个线程同时创建节点==>发生异常)
//发生异常说明path已存在==>已被抢走,在catch中返回false,代表没有抢到资源
//一切顺利,没有异常,直接返回真值==>抢到资源
try {
zkClient.createEphemeral(PATH);
return true;
}catch (Exception e){
return false;
}
}
@Override
void waitlock() {
//配置监听器
//为什么先配置监听器?
IZkDataListener iZkDataListener = new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
if(countDownLatch!=null){
//信号量countDown减一 唤醒线程
countDownLatch.countDown();
}
}
};
//path为监听的节点 iZkDataListener为监听器
System.out.print(Thread.currentThread().getId()+"开启了监听器!");
zkClient.subscribeDataChanges(PATH,iZkDataListener);
//判断节点是否存在,如果存在就让我改成await
if(zkClient.exists(PATH)){
try {
countDownLatch = new CountDownLatch(1);
System.out.print(Thread.currentThread().getId()+"我进等待了!");
countDownLatch.await();
System.out.println(Thread.currentThread().getId()+"我等到了!");
}catch (Exception e){
e.printStackTrace();
}
}
}
}
问题1:为什么要先配置监听器?
如果后配置监听器会导致监听器未启动,线程便进入await,await等不到countDownLatch.countDown();就会一直处于等待状态,陷入死锁。
问题2:
如果按照1的描述,那么就不应该有线程能跳出await。为什么后面的代码还会执行呢?请看下图
可以跳出监听器的都是在之前一次进入的时候,所占的资源没有被占用,所以已经开启了监听器。这个线程不结束,监听器就不结束。在接下来争抢资源的过程中,监听器是已存在的,所以可以跳出await。
3.以上代码是正确的逻辑,错误的逻辑是在判断之后配置监听器。