排它锁(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; } }