1,实现分布式锁思路
因为Zookeeper节点路径保持唯一,不允许重复 且有临时节点特性连接关闭后当前节点会自动消失,从而实现分布式锁。
2,实现原理
- 多请求同时创建相同的节点(lockPath),只要谁能够创建成功 谁就能够获取到锁;
- 如果创建节点的时候,突然该节点已经被其他请求创建的话则直接等待;
- 只要能够创建节点成功,则开始进入到正常业务逻辑操作,其他没有获取锁进行等待;
- 正常业务逻辑流程执行完后,调用zk关闭连接方式释放锁,从而是其他的请求开始进入到获取锁的资源。
public interface Lock {
/**
* 获取锁
*/
public void getLock();
/**
* 释放锁
*/
public void unLock();
}
abstract class AbstractTemplzateLock implements Lock {
@Override
public void getLock() {
// 模版方法 定义共同抽象的骨架
if (tryLock()) {
System.out.println(">>>" + Thread.currentThread().getName() + ",获取锁成功");
} else {
// 开始实现等待
waitLock();// 事件监听
// 重新获取
getLock();
}
}
@Override
public void unLock() {
unImplLock();
}
/**
* 获取锁
*
* @return
*/
protected abstract boolean tryLock();
/**
* 等待锁
*
* @return
*/
protected abstract void waitLock();
/**
* 释放锁
*
* @return
*/
protected abstract void unImplLock();
}
public class ZkTemplzateImplLock extends AbstractTemplzateLock {
//参数1 连接地址
private static final String ADDRES = "localhost:2181";
// 参数2 zk超时时间
private static final int TIMEOUT = 5000;
// 创建我们的zk连接
private ZkClient zkClient = new ZkClient(ADDRES, TIMEOUT);
/**
* 共同的创建临时节点
*/
private String lockPath = "/lockPath";
private CountDownLatch countDownLatch = null;
@Override
protected boolean tryLock() {
// 获取锁的思想:多个jvm同时创建临时节点,只要谁能够创建成功 谁能够获取到锁
try {
zkClient.createEphemeral(lockPath);
return true;
} catch (Exception e) {
// // 如果创建已经存在的话
// e.printStackTrace();
return false;
}
}
@Override
protected void waitLock() {
// 1.使用事件监听 监听lockPath节点是否已经被删除,如果被删除的情况下 有可以重新的进入到获取锁的权限
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) {
countDownLatch.countDown();// 计数器变为0
}
}
};
zkClient.subscribeDataChanges(lockPath, iZkDataListener);
// 2.使用countDownLatch等待
if (countDownLatch == null) {
countDownLatch = new CountDownLatch(1);
}
try {
countDownLatch.await();// 如果当前计数器不是为0 就一直等待
} catch (Exception e) {
}
// 3. 如果当前节点被删除的情况下,有需要重新进入到获取锁
zkClient.unsubscribeDataChanges(lockPath, iZkDataListener);
}
@Override
protected void unImplLock() {
if (zkClient != null) {
zkClient.close();
System.out.println(Thread.currentThread().getName() + ",释放了锁>>>");
}
}
}
测试
public class OrderNumGenerator {
/**
* 序号
*/
private static int count;
/**
* 生成我们的时间戳 为订单号码
*
* @return
*/
public String getNumber() {
SimpleDateFormat simpt = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
try {
Thread.sleep(30);
} catch (Exception e) {
}
return simpt.format(new Date()) + "-" + ++count;
}
}
public class OrderService implements Runnable {
private OrderNumGenerator orderNumGenerator = new OrderNumGenerator();
private Lock lock = new ZkTemplzateImplLock();
@Override
public void run() {
getNumber();
}
private void getNumber() {
try {
lock.getLock();
Thread.sleep(50);
String number = orderNumGenerator.getNumber();
System.out.println(Thread.currentThread().getName() + ",获取的number:" + number);
// 如果zk超时了,有做数据库写的操作统一直接回滚
} catch (Exception e) {
} finally {
lock.unLock();
}
}
// ZkTemplzateImplLock父亲 模版类 AbstractTemplzateLock 父亲 Lock
}
public class Test001 {
public static void main(String[] args) {
// OrderService orderService = new OrderService();
for (int i = 0; i < 100; i++) {
new Thread(new OrderService()).start();
}
// 单个jvm中多线程同时生成订单号码如果发生重复 如何解决 synchronized或者是lock锁
// 如果在多个jvm中同时生成订单号码如果发生重复如何解决
// 注意synchronized或者是lock锁 只能够在本地的jvm中有效
// 分布式锁的概念
}
}