思考:分布式锁实现的逻辑
1.争抢锁,只有一个线程可以获得锁。
2.获得锁的线程挂了,产生死锁。解决:使用临时节点(伴随客户端的session,session删除时临时节点也会删除)。
3.获得锁的线程执行成功,释放锁。
4.上面两点,锁被删除或者释放,其他线程如何知道。
(1.主动轮询,弊端:延迟,压力 。 2.watch,解决延迟问题,弊端:压力)
5.sequence(序列节点) + watch:watch前一个,最小的获得锁,一旦最小的释放锁,只给watch他的那个发事件回调。
public class WatchCallBack implements Watcher ,AsyncCallback.StringCallback ,AsyncCallback.Children2Callback ,AsyncCallback.StatCallback {
ZooKeeper zk ;
String threadName;
CountDownLatch cc = new CountDownLatch(1);
String pathName;
public String getPathName() {
return pathName;
}
public void setPathName(String pathName) {
this.pathName = pathName;
}
public String getThreadName() {
return threadName;
}
public void setThreadName(String threadName) {
this.threadName = threadName;
}
public ZooKeeper getZk() {
return zk;
}
public void setZk(ZooKeeper zk) {
this.zk = zk;
}
public void tryLock(){
try {
System.out.println(threadName + "create....");
//10个线程并行去在/lock目录下创建临时有序的锁节点
zk.create("/lock",threadName.getBytes(),ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.EPHEMERAL_SEQUENTIAL,this,"abc");
//获得锁的线程去 -1,获得锁之前都阻塞
cc.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void unLock(){
try {
zk.delete(pathName,-1);
System.out.println(threadName + "释放锁....");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
/**
* watch的回调方法
* @param event 事件
*/
@Override
public void process(WatchedEvent event) {
//如果第一个线程锁释放了,只有他后面的一个收到了回调事件
//如果中间的某个挂了,也能造成他后边的收到这个通知,从而让他后边那个跟去watch他之前的
switch (event.getType()) {
case None:
break;
case NodeCreated:
break;
case NodeDeleted:
//从新获取锁节点的集合
zk.getChildren("/",false,this,"sdf");
break;
case NodeDataChanged:
break;
case NodeChildrenChanged:
break;
}
}
/**
*在/lock目录下创建临时有序的锁节点的回调
*/
@Override
public void processResult(int rc, String path, Object ctx, String name) {
if(name != null ){
System.out.println(threadName + "create node : " + name);
pathName = name;
//10个线程去获得各自锁节点之前的锁节点
//此处不需要watch,因为不需要关注锁目录的变化,只是要关注他前面的锁节点的变化
zk.getChildren("/", false, this, "sdf");
}
}
/**
* getChildren的回调
*/
@Override
public void processResult(int rc, String path, Object ctx, List<String> children, Stat stat) {
//将自己能看到的锁节点排序
Collections.sort(children);
//获得自己在集合中的位置
int i = children.indexOf(pathName.substring(1));
//判断自己是不是第一个
if(i == 0){
System.out.println(threadName +"我是最小的锁节点....");
try {
zk.setData("/",threadName.getBytes(),-1);
//countDown,让tryLock不在阻塞,自己获得锁
cc.countDown();
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
//如果不是第一个,就给自己前面的一个添加一个watch,如果他发生变化,则继续走到这个方法里判断
zk.exists("/"+children.get(i-1),this,this,"sdf");
}
}
/**
* 给自己前面一个添加个watch时的回调
*/
@Override
public void processResult(int rc, String path, Object ctx, Stat stat) {
}
}
public class TestLock {
ZooKeeper zk;
@Before
public void conn (){
zk = ZKUtils.getZK();
}
@After
public void close (){
try {
zk.close();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void lock(){
for (int i = 0; i < 10; i++) {
new Thread(){
@Override
public void run() {
//每个线程都有独立的 WatchCallBack
WatchCallBack watchCallBack = new WatchCallBack();
watchCallBack.setZk(zk);
String threadName = Thread.currentThread().getName();
watchCallBack.setThreadName(threadName);
//每一个线程尝试抢锁
watchCallBack.tryLock();
//获得锁后执行业务
System.out.println(threadName + "获得锁,执行业务...");
//释放锁
watchCallBack.unLock();
}
}.start();
}
//让主线程阻塞
while(true){
}
}
}