排它锁(Exclusive Locks,简称X锁)又称之为独占锁,是一种基本的锁类型。排他锁的核心就是如何保证仅有一个线程获取到锁,并且在锁释放后,可以及时地通知到其他等待获取锁定的线程。下面使用ZK实现了一个简单的排它锁。
定义锁
在ZK下定义一个临时节点节点表示锁
/**排它锁节点**/
private final String EXCLUSIVE_LOCK = "/zk-demo/lock";
获取锁
在需要获取锁时,所有客户端都需要视图通过调用create()方法在ZK上创建这个临时节点。zk保证所有客户端中仅有一个客户端可以创建成功,如果创建成功的客户端则认为他获取了锁。同时没有获取到则需要向这个节点注册一个监听器,监听其他客户端释放锁。
释放锁
我们定义的锁是一个临时节点,有两种情况可以释放锁。
- 当前客户端发生宕机,也就是session断开则这个临时节点被移除。
- 正常业务逻辑执行完成后主动删除自己创建的临时节点。
无论在什么情况下移除了lock这个临时节点,ZK都会通知所有在/zk-demo节点上注册的子节点变更监听器。在客户端接收到通知时可以再次发起获取分布式锁的尝试
/**
* 分布式锁服务接口,该接口定义了如下功能
* <ul>
* <li> tryLock 一直等待锁</li>
* <li> tryLock 等待一段时间,如果超时则会调用回调方法expire()</li>
* </ul>
*
* @author zhangwei_david
* @version $Id: DistributedLockService.java, v 0.1 2015年7月1日 下午9:03:33 zhangwei_david Exp $
*/
public interface DistributedLockService {
/**
* 试图获取分布式锁,如果返回true则表示获取了锁
*
* @param callback 回调接口
*/
public void tryLock(CallBack callback);
/**
* 视图获取分布式锁,如果在指定timeout时间后容然未能够获取到锁则返回
*
* @param callback
* @param timeout
*/
public void tryLock(CallBack callback, long timeout);
/**
* 回调处理接口
*
* @author zhangwei_david
* @version $Id: DistributedLockService.java, v 0.1 2015年7月2日 上午10:59:22 zhangwei_david Exp $
*/
public interface CallBack {
/**
* 获取分布式锁后回调方法
*/
public void locked();
/**
* 获取分布式锁超时回调方法
*/
public void expire();
}
}
/**
* 分布式锁服务实现类
* @author zhangwei_david
* @version $Id: DistributedLockServiceImpl.java, v 0.1 2015年7月1日 下午9:05:48 zhangwei_david Exp $
*/
@Component
public class DistributedLockServiceImpl implements DistributedLockService {
private static final String ROOT = "/zk-demo";
/**锁的临时节点**/
private static final String LOCK = "lock";
/**排它锁节点**/
private static final String EXCLUSIVE_LOCK = ROOT + "/" + LOCK;
private int sessionTimeout = 3000;
/**
* @see com.david.common.distributedLock.DistributedLockService#tryLock(com.david.common.distributedLock.DistributedLockService.CallBack, long)
*/
public void tryLock(final CallBack callback, long timeout) {
try {
final long expireTime = timeout > 0 ? System.currentTimeMillis() + timeout : -1;
final ZooKeeper zk = getZooKeeper();
//向根节点注册一个子节点变化监听器
List<String> nodes = zk.getChildren(ROOT, new Watcher() {
public void process(WatchedEvent event) {
// 排它锁已经被释放,则视图获取锁
if (event.getState() == KeeperState.SyncConnected
&& event.getType() == EventType.NodeChildrenChanged) {
doLock(zk, callback, expireTime);
}
}
});
// 没有人获取锁则视图获取锁
if (!nodes.contains(LOCK)) {
doLock(zk, callback, expireTime);
}
} catch (Exception e) {
}
}
/**
*
* @see com.david.common.distributedLock.DistributedLockService#tryLock(com.david.common.distributedLock.DistributedLockService.CallBack)
*/
public void tryLock(final CallBack callback) {
tryLock(callback, -1);
}
/**
* 具体执行分布式锁,如果拥有分布式锁则执行callback回调,然后释放锁
*
* @param zk
* @param callback
* @param expireTime 过期时间
*/
private void doLock(ZooKeeper zk, CallBack callback, long expireTime) {
try {
if (expireTime > 0 && System.currentTimeMillis() > expireTime) {
callback.expire();
return;
}
String path = zk
.create(EXCLUSIVE_LOCK, null, Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
System.out.println(path);
if (path != null) {
callback.locked();
zk.delete(EXCLUSIVE_LOCK, -1);
}
} catch (Exception e) {
} finally {
try {
zk.close();
} catch (InterruptedException e) {
}
}
}
/**
* 获取ZooKeeper
*
* @param sessionTimeout
* @return
* @throws Exception
*/
private ZooKeeper getZooKeeper() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
ZooKeeper zk = new ZooKeeper("localhost:2181", sessionTimeout, new Watcher() {
public void process(WatchedEvent event) {
if (KeeperState.SyncConnected == event.getState()) {
//如果客户端已经建立连接闭锁减一
latch.countDown();
}
}
});
// 等待连接建立
latch.await();
return zk;
}
/**
* Getter method for property <tt>sessionTimeout</tt>.
*
* @return property value of sessionTimeout
*/
public int getSessionTimeout() {
return sessionTimeout;
}
/**
* Setter method for property <tt>sessionTimeout</tt>.
*
* @param sessionTimeout value to be assigned to property sessionTimeout
*/
public void setSessionTimeout(int sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
}