文章转载自https://blog.csdn.net/koflance/article/details/78616206html
一、互斥锁mutex lock
顾名思义就是排它锁,同一时间只容许一个客户端执行。node
实现步骤:apache
- 首先,建立一个lock node,例如“locknode”
- 其次,客户端lock执行如下方式:
- 建立(create)一个有序临时节点,例如“locknode/guid-lock-”,其中guid能够是你客户端的惟一识别序号,若是发生前面说的建立失败问题,须要使用guid进行手动检查。
- 调用getChildren(watch=false)获取获取子节点列表,注意wtach设置为false,以免羊群效应(Herd Effect),即同时收到太多无效节点删除通知。
- 从这个列表中,判断本身建立的节点序号是不是最小,若是是则直接返回true,不然继续往下走。
- 从步骤2中获取的list中选取排在当前节点前一位的节点,调用exist(watch=true)方法。
- 若是exist返回false,则回到步骤2;
- 若是exist返回true,则等待exist的哨兵(watch)回调通知,收到通知后再执行步骤2.
- 最后,客户端unlock只须要调用delete删除掉节点便可。
节点操做示意图:ui

流程图:spa

优势:.net
- 避免了轮询和超时控制
- 每次一个子节点的删除动做,只会触发惟一一个客户端的watch动做,从而避免了羊群效应
- 便于观测
缺点:server
- 没有解决锁重入问题,由于采用的是有序临时节点,所以屡次调用create并不会触发KeeperException.NodeExists异常,从而没法实现锁重入功能。若是须要解决,则在步骤1时,须要先进行判断当前节点是否已经存在,即调用getChildren(watch=false),判断当前节点是否已经建立(配合guid),已经建立,则直接从步骤3开始,没有建立则从步骤1开始。
- 这是一个公平锁,没法实现非公平锁。参考[4]实现了一个非公平锁
注意:htm
若是一个节点建立了一个sequential ephemeral nodes,可是在server返回建立成功的时候,server挂了,此时客户端须要从新链接,从新链接后会话依然有效,但其建立的临时节点却没有删除。解决方式就是在每次建立时create,若是发生失败,客户端须要getChildren(),进行手动检查是否获取锁,这个时候就须要使用guid。blog
二、共享锁Shared Locks或读写锁Read/Write Locks
Read读锁是共享锁,Write写锁是排它锁,当没有写时,容许多个read实例获取读锁,当有一个write实例得到写锁时,则不容许任何其余write实例和read实例得到锁。图片
实现步骤:
- 首先,建立一个lock node,例如“locknode”
- 获取read锁步骤:
- 建立(create)一个有序临时节点,例如“locknode/read-guid-lock-”,其中guid能够是你客户端的惟一识别序号,若是发生前面说的建立失败问题,须要使用guid进行手动检查。
- 调用getChildren(watch=false)获取获取子节点列表,注意wtach设置为false,以免羊群效应(Herd Effect),即同时收到太多无效节点删除通知。
- 从这个列表中,判断是否有序号比本身小、且路径名以“write-”开头的节点,若是没有,则直接获取读锁,不然继续以下步骤。
- 从步骤2中获取的list中选取排在当前节点前一位的、且路径名以“write-”开头的节点,调用exist(watch=true)方法。
- 若是exist返回false,则回到步骤2。
- 若是exist返回true,则等待exist的哨兵(watch)回调通知,收到通知后再执行步骤2。
- 获取write锁步骤:
- 建立(create)一个有序临时节点,例如“locknode/write-guid-lock-”,其中guid能够是你客户端的惟一识别序号,若是发生前面说的建立失败问题,须要使用guid进行手动检查。
- 调用getChildren(watch=false)获取获取子节点列表,注意wtach设置为false,以免羊群效应(Herd Effect),即同时收到太多无效节点删除通知。
- 从这个列表中,判断本身建立的节点序号是不是最小,若是是则直接返回true,不然继续往下走。
- 从步骤2中获取的list中选取排在当前节点前一位的节点,调用exist(watch=true)方法。
- 若是exist返回false,则回到步骤2;
- 若是exist返回true,则等待exist的哨兵(watch)回调通知,收到通知后再执行步骤2.
- 最后,客户端unlock只须要调用delete删除掉节点便可。
节点操做示意图:

流程图:


优势:
- 避免了轮询和超时控制
- 每次一个子节点的删除动做,只会触发惟一一个客户端的watch动做,从而避免了羊群效应
- 便于观测
缺点:
- 没有解决锁重入问题,由于采用的是有序临时节点,所以屡次调用create并不会触发KeeperException.NodeExists异常,从而没法实现锁重入功能。若是须要解决,则在步骤1时,须要先进行判断当前节点是否已经存在,即调用getChildren(watch=false),判断当前节点是否已经建立(配合guid),已经建立,则直接从步骤3开始,没有建立则从步骤1开始。
- 当有很是多的read节点在等待一个write节点删除通知时,一旦write节点删除,将会触发很是多的read节点被调用,不过这种状况没法避免。
可撤销和超时问题
当前的读写锁并无考虑读锁可撤销和超时问题,如何让读锁主动放弃,如何判断超时等,我想可行的方案仍是在客户端本身处理,若是其余客户端想让前面的节点放弃锁,能够在节点写入unlock信息,让持有锁的客户端监听该变化,收到unlock信息,本身主动放弃对锁的持有。
三、参考
[1] http://zookeeper.apache.org/doc/trunk/recipes.html#sc_recipes_Locks
[2] http://blog.csdn.net/xubo_zhang/article/details/8506163
[3] http://netcome.iteye.com/blog/1474255 一个比较好的介绍
[4] http://blog.csdn.net/nimasike/article/details/51567653#reply 实现了一种非公平锁