之前实现的分布式锁只是利用了zookeeper的临时节点,在大集群的环境下并不适用,会出现“惊群”效应:每次节点删除,所有的调用者都来获取锁,zookeeper负载太大,也造成资源不必要的浪费;这时可以为调用者定一个顺序(zookeeper的临时顺序节点),当调用者自己的编号是所有节点中最小的,那设定它取得了锁,否则监听最小的节点,这个节点删除时,重新尝试获得锁。
本例中使用了ThreadLocal,它并不是把数据存储在它内部,它只是作为一个key,把真实数据放在Thread的一个Map<ThreadLocal, Object>里的,get的时候也是从这个map里取, 它是操作线程副本变量的中介。详见
Java并发编程:深入剖析ThreadLocal
/**
* zookeeper锁实现(临时顺序结点)
* @author skymr
*
*/
public class ZookeeperLock1 implements Lock, Watcher{
public ZookeeperLock1(String url, int sessionTimeOut, String path){
this.parrentPath = path;
try {
//url是zookepper服务器的地址
zk = new ZooKeeper(url, sessionTimeOut, this);
latch.await();
} catch (Exception e) {
e.printStackTrace();
}
}
//zk客户端
private ZooKeeper zk;
//结点路径
private String parrentPath;
//用于初始化zk的,zk连接是异步的,但连接成功后才能进行调用
private CountDownLatch latch = new CountDownLatch(1);
private static ThreadLocal<String> currentNodePath = new ThreadLocal<String>();
public void lock() {
if(!tryLock()){
String mypath = currentNodePath.get();
//如果尝试加锁失败,则进入等待
synchronized(mypath){
System.out.println(Thread.currentThread().getName() +" lock失败,进入等待");
try {
mypath.wait();
} catch (Exception e) {
}
System.out.println(Thread.currentThread().getName() +" lock等待完成");
}
//等待别人释放锁后,自己再去加锁
lock();
}
else{
System.out.println(Thread.currentThread().getName() +" lock成功");
}
}
public void lockInterruptibly() throws InterruptedException {
}
public boolean tryLock() {
try {
//加锁代码是创建一个节点
String mypath = currentNodePath.get();
if(mypath == null){
mypath = zk.create(parrentPath + "/", "111".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
currentNodePath.set(mypath);
}
final String currentPath = mypath;
List<String> allNodes = zk.getChildren(parrentPath, false);
Collections.sort(allNodes);
//不抛异常就表示创建成功啦
String nodeName = mypath.substring((parrentPath + "/").length());
if(allNodes.get(0).equals(nodeName)){
//当前结点是最小的节点,获取锁成功
return true;
}
else{
//监听最小的结点
String targetNodeName = parrentPath + "/" + allNodes.get(0);
System.out.println(Thread.currentThread().getName() +" 需要等待节点删除" + targetNodeName);
zk.exists(targetNodeName, new Watcher() {
public void process(WatchedEvent event) {
if(event.getType() == EventType.NodeDeleted){
synchronized(currentPath){
currentPath.notify();
}
System.out.println(Thread.currentThread().getName() +" 通知Lock等待中的线程重试加锁");
}
}
});
}
return false;
} catch (Exception e) {
return false;
}
}
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
public void unlock() {
try {
//释放锁,删除节点
String mypath = currentNodePath.get();
if(mypath != null){
System.out.println(Thread.currentThread().getName() +" 释放锁");
zk.delete(mypath, -1);
currentNodePath.remove();
}
} catch (Exception e) {
}
}
public Condition newCondition() {
return null;
}
public void process(WatchedEvent event) {
System.out.println(event);
if(event.getType() == EventType.None){
//当连接上了服务器后,初始化完成
latch.countDown();
}
}
}
说明:
trylock方法中首先检查线程副本变量中,是否存在zk节点,若存在,表示当前线程已创建结点,不能再创建结点了,此次调用只是为了看看是否获取了锁。
在父结点中找到最小的结点,如果最小结点就是自己创建的结点,表示获取锁成功,否则监听最小结点
如果监听的结点被删除,收到 event的类型为NodeDeleted,则通知正在监听此结点的其他结点,让其退出等待
lock方法:尝试加锁,加锁失败进入等待,等待完成后重试加锁。
wait方法是针对当前结点名称调用,对当前结点名称进行synchronized,而不是用zk对象,是因为在同一个进程的不同线程使用同一个zookeeper客户端时(不同线程共享ZookeeperLock1对象), 如果对zk进行notifyAll的话,那所有调用者线程都被唤起,都去重新尝试获取锁,影响性能。在同一服务器中同一进程使用同一个ZookeeperLock1对象,即不同线程使用的是同一个zk客户端,节省了zk服务器的资源,如果每个线程都单独有个zk连接,那zk服务器维护的连接数量太大。
释放所的时候把线程副本变量中的结点名称删除掉,一是为了节省内存空间,二是清除锁数据,如果不清除,这条数据存在的时间一长,等下次同一线程进行lock的时候就不会去重新create节点了,使用的是以前的节点,但这个节点在zk中又不存在,会引发重大问题。
测试:
public class LockTest {
// private static Lock lock = new ReentrantLock();
private static Lock lock = new ZookeeperLock1("localhost", 3000, "/node");
public static void main(String[] args) throws Exception{
for(int i = 0; i < 10; i++){
new Thread(){
public void run(){
// Lock lock = new ZookeeperLock1("localhost", 3000, "/node");
try{
lock.lock();
System.out.println(Thread.currentThread().getName() + "开始执行");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName() + "执行完成 ");
}
finally{
lock.unlock();
}
}
}.start();
}
}
}
测试结果:
WatchedEvent state:SyncConnected type:None path:null
Thread-5 需要等待节点删除/node/0000000021
Thread-9 需要等待节点删除/node/0000000021
Thread-1 需要等待节点删除/node/0000000021
Thread-4 需要等待节点删除/node/0000000021
Thread-0 需要等待节点删除/node/0000000021
Thread-2 需要等待节点删除/node/0000000021
Thread-3 需要等待节点删除/node/0000000021
Thread-7 需要等待节点删除/node/0000000021
Thread-6 lock成功
Thread-6开始执行
Thread-8 需要等待节点删除/node/0000000021
Thread-6执行完成
Thread-6 释放锁
Thread-7 lock失败,进入等待
Thread-3 lock失败,进入等待
Thread-2 lock失败,进入等待
Thread-8 lock失败,进入等待
Thread-5 lock失败,进入等待
Thread-0 lock失败,进入等待
Thread-4 lock失败,进入等待
Thread-1 lock失败,进入等待
Thread-9 lock失败,进入等待
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
Thread-3 lock等待完成
Thread-2 lock等待完成
Thread-7 lock等待完成
Thread-9 lock等待完成
Thread-0 lock等待完成
Thread-4 lock等待完成
Thread-5 lock等待完成
Thread-1 lock等待完成
Thread-8 lock等待完成
Thread-3 需要等待节点删除/node/0000000022
Thread-2 需要等待节点删除/node/0000000022
Thread-7 需要等待节点删除/node/0000000022
Thread-9 需要等待节点删除/node/0000000022
Thread-2 lock失败,进入等待
Thread-3 lock失败,进入等待
Thread-4 需要等待节点删除/node/0000000022
Thread-1 需要等待节点删除/node/0000000022
Thread-7 lock失败,进入等待
Thread-0 需要等待节点删除/node/0000000022
Thread-8 lock成功
Thread-8开始执行
Thread-5 需要等待节点删除/node/0000000022
Thread-1 lock失败,进入等待
Thread-9 lock失败,进入等待
Thread-4 lock失败,进入等待
Thread-0 lock失败,进入等待
Thread-5 lock失败,进入等待
Thread-8执行完成
Thread-8 释放锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
Thread-7 lock等待完成
Thread-3 lock等待完成
Thread-2 lock等待完成
Thread-1 lock等待完成
Thread-0 lock等待完成
Thread-4 lock等待完成
Thread-9 lock等待完成
Thread-5 lock等待完成
Thread-7 需要等待节点删除/node/0000000023
Thread-3 lock成功
Thread-3开始执行
Thread-2 需要等待节点删除/node/0000000023
Thread-7 lock失败,进入等待
Thread-0 需要等待节点删除/node/0000000023
Thread-4 需要等待节点删除/node/0000000023
Thread-9 需要等待节点删除/node/0000000023
Thread-1 需要等待节点删除/node/0000000023
Thread-2 lock失败,进入等待
Thread-5 需要等待节点删除/node/0000000023
Thread-0 lock失败,进入等待
Thread-4 lock失败,进入等待
Thread-9 lock失败,进入等待
Thread-1 lock失败,进入等待
Thread-5 lock失败,进入等待
Thread-3执行完成
Thread-3 释放锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
Thread-4 lock等待完成
Thread-5 lock等待完成
Thread-9 lock等待完成
Thread-2 lock等待完成
Thread-7 lock等待完成
main-EventThread 通知Lock等待中的线程重试加锁
Thread-1 lock等待完成
Thread-0 lock等待完成
Thread-4 需要等待节点删除/node/0000000024
Thread-5 需要等待节点删除/node/0000000024
Thread-9 需要等待节点删除/node/0000000024
Thread-2 需要等待节点删除/node/0000000024
Thread-7 需要等待节点删除/node/0000000024
Thread-0 需要等待节点删除/node/0000000024
Thread-4 lock失败,进入等待
Thread-1 lock成功
Thread-1开始执行
Thread-5 lock失败,进入等待
Thread-9 lock失败,进入等待
Thread-2 lock失败,进入等待
Thread-7 lock失败,进入等待
Thread-0 lock失败,进入等待
Thread-1执行完成
Thread-1 释放锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
Thread-2 lock等待完成
Thread-7 lock等待完成
Thread-5 lock等待完成
Thread-4 lock等待完成
Thread-9 lock等待完成
Thread-0 lock等待完成
Thread-2 需要等待节点删除/node/0000000025
Thread-7 lock成功
Thread-7开始执行
Thread-0 需要等待节点删除/node/0000000025
Thread-2 lock失败,进入等待
Thread-5 需要等待节点删除/node/0000000025
Thread-4 需要等待节点删除/node/0000000025
Thread-9 需要等待节点删除/node/0000000025
Thread-0 lock失败,进入等待
Thread-5 lock失败,进入等待
Thread-4 lock失败,进入等待
Thread-9 lock失败,进入等待
Thread-7执行完成
Thread-7 释放锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
Thread-2 lock等待完成
Thread-0 lock等待完成
Thread-9 lock等待完成
Thread-4 lock等待完成
Thread-5 lock等待完成
Thread-2 需要等待节点删除/node/0000000026
Thread-0 lock成功
Thread-0开始执行
Thread-9 需要等待节点删除/node/0000000026
Thread-4 需要等待节点删除/node/0000000026
Thread-5 需要等待节点删除/node/0000000026
Thread-2 lock失败,进入等待
Thread-9 lock失败,进入等待
Thread-4 lock失败,进入等待
Thread-5 lock失败,进入等待
Thread-0执行完成
Thread-0 释放锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
Thread-2 lock等待完成
Thread-4 lock等待完成
Thread-5 lock等待完成
Thread-9 lock等待完成
Thread-2 lock成功
Thread-2开始执行
Thread-4 需要等待节点删除/node/0000000027
Thread-5 需要等待节点删除/node/0000000027
Thread-9 需要等待节点删除/node/0000000027
Thread-4 lock失败,进入等待
Thread-5 lock失败,进入等待
Thread-9 lock失败,进入等待
Thread-2执行完成
Thread-2 释放锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
Thread-9 lock等待完成
Thread-5 lock等待完成
Thread-4 lock等待完成
Thread-9 需要等待节点删除/node/0000000028
Thread-5 需要等待节点删除/node/0000000028
Thread-4 lock成功
Thread-4开始执行
Thread-9 lock失败,进入等待
Thread-5 lock失败,进入等待
Thread-4执行完成
Thread-4 释放锁
main-EventThread 通知Lock等待中的线程重试加锁
main-EventThread 通知Lock等待中的线程重试加锁
Thread-9 lock等待完成
Thread-5 lock等待完成
Thread-9 lock成功
Thread-9开始执行
Thread-5 需要等待节点删除/node/0000000029
Thread-5 lock失败,进入等待
Thread-9执行完成
Thread-9 释放锁
main-EventThread 通知Lock等待中的线程重试加锁
Thread-5 lock等待完成
Thread-5 lock成功
Thread-5开始执行
Thread-5执行完成
Thread-5 释放锁
最近wait/notify机制好像是被什么机制替代了呢,pack/upack?忘了。