zookeeper分布式锁代码实现

   之前实现的分布式锁只是利用了zookeeper的临时节点,在大集群的环境下并不适用,会出现“惊群”效应:每次节点删除,所有的调用者都来获取锁,zookeeper负载太大,也造成资源不必要的浪费;这时可以为调用者定一个顺序(zookeeper的临时顺序节点),当调用者自己的编号是所有节点中最小的,那设定它取得了锁,否则监听最小的节点,这个节点删除时,重新尝试获得锁。

  本例中使用了ThreadLocal,它并不是把数据存储在它内部,它只是作为一个key,把真实数据放在Thread的一个Map<ThreadLocal, Object>里的,get的时候也是从这个map里取, 它是操作线程副本变量的中介。详见

Java并发编程:深入剖析ThreadLocal

 
  1.  
  2. /**

  3. * zookeeper锁实现(临时顺序结点)

  4. * @author skymr

  5. *

  6. */

  7. public class ZookeeperLock1 implements Lock, Watcher{

  8.  
  9.  
  10. public ZookeeperLock1(String url, int sessionTimeOut, String path){

  11. this.parrentPath = path;

  12. try {

  13. //url是zookepper服务器的地址

  14. zk = new ZooKeeper(url, sessionTimeOut, this);

  15. latch.await();

  16. } catch (Exception e) {

  17. e.printStackTrace();

  18. }

  19. }

  20.  
  21. //zk客户端

  22. private ZooKeeper zk;

  23.  
  24. //结点路径

  25. private String parrentPath;

  26.  
  27. //用于初始化zk的,zk连接是异步的,但连接成功后才能进行调用

  28. private CountDownLatch latch = new CountDownLatch(1);

  29.  
  30. private static ThreadLocal<String> currentNodePath = new ThreadLocal<String>();

  31.  
  32. public void lock() {

  33. if(!tryLock()){

  34. String mypath = currentNodePath.get();

  35. //如果尝试加锁失败,则进入等待

  36. synchronized(mypath){

  37. System.out.println(Thread.currentThread().getName() +" lock失败,进入等待");

  38. try {

  39. mypath.wait();

  40. } catch (Exception e) {

  41. }

  42. System.out.println(Thread.currentThread().getName() +" lock等待完成");

  43. }

  44. //等待别人释放锁后,自己再去加锁

  45. lock();

  46. }

  47. else{

  48. System.out.println(Thread.currentThread().getName() +" lock成功");

  49. }

  50. }

  51.  
  52. public void lockInterruptibly() throws InterruptedException {

  53.  
  54. }

  55.  
  56. public boolean tryLock() {

  57. try {

  58. //加锁代码是创建一个节点

  59. String mypath = currentNodePath.get();

  60. if(mypath == null){

  61. mypath = zk.create(parrentPath + "/", "111".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);

  62. currentNodePath.set(mypath);

  63. }

  64. final String currentPath = mypath;

  65. List<String> allNodes = zk.getChildren(parrentPath, false);

  66. Collections.sort(allNodes);

  67. //不抛异常就表示创建成功啦

  68. String nodeName = mypath.substring((parrentPath + "/").length());

  69. if(allNodes.get(0).equals(nodeName)){

  70. //当前结点是最小的节点,获取锁成功

  71. return true;

  72. }

  73. else{

  74. //监听最小的结点

  75. String targetNodeName = parrentPath + "/" + allNodes.get(0);

  76. System.out.println(Thread.currentThread().getName() +" 需要等待节点删除" + targetNodeName);

  77. zk.exists(targetNodeName, new Watcher() {

  78. public void process(WatchedEvent event) {

  79. if(event.getType() == EventType.NodeDeleted){

  80. synchronized(currentPath){

  81. currentPath.notify();

  82. }

  83. System.out.println(Thread.currentThread().getName() +" 通知Lock等待中的线程重试加锁");

  84. }

  85. }

  86. });

  87. }

  88. return false;

  89. } catch (Exception e) {

  90. return false;

  91. }

  92. }

  93.  
  94. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {

  95. return false;

  96. }

  97.  
  98. public void unlock() {

  99. try {

  100. //释放锁,删除节点

  101. String mypath = currentNodePath.get();

  102. if(mypath != null){

  103. System.out.println(Thread.currentThread().getName() +" 释放锁");

  104. zk.delete(mypath, -1);

  105. currentNodePath.remove();

  106. }

  107. } catch (Exception e) {

  108. }

  109. }

  110.  
  111. public Condition newCondition() {

  112. return null;

  113. }

  114.  
  115. public void process(WatchedEvent event) {

  116. System.out.println(event);

  117. if(event.getType() == EventType.None){

  118. //当连接上了服务器后,初始化完成

  119. latch.countDown();

  120. }

  121. }

  122.  
  123. }

说明:

trylock方法中首先检查线程副本变量中,是否存在zk节点,若存在,表示当前线程已创建结点,不能再创建结点了,此次调用只是为了看看是否获取了锁。

在父结点中找到最小的结点,如果最小结点就是自己创建的结点,表示获取锁成功,否则监听最小结点

如果监听的结点被删除,收到 event的类型为NodeDeleted,则通知正在监听此结点的其他结点,让其退出等待

lock方法:尝试加锁,加锁失败进入等待,等待完成后重试加锁。

wait方法是针对当前结点名称调用,对当前结点名称进行synchronized,而不是用zk对象,是因为在同一个进程的不同线程使用同一个zookeeper客户端时(不同线程共享ZookeeperLock1对象), 如果对zk进行notifyAll的话,那所有调用者线程都被唤起,都去重新尝试获取锁,影响性能。在同一服务器中同一进程使用同一个ZookeeperLock1对象,即不同线程使用的是同一个zk客户端,节省了zk服务器的资源,如果每个线程都单独有个zk连接,那zk服务器维护的连接数量太大。

释放所的时候把线程副本变量中的结点名称删除掉,一是为了节省内存空间,二是清除锁数据,如果不清除,这条数据存在的时间一长,等下次同一线程进行lock的时候就不会去重新create节点了,使用的是以前的节点,但这个节点在zk中又不存在,会引发重大问题。

测试:

 
  1.  
  2. public class LockTest {

  3.  
  4. // private static Lock lock = new ReentrantLock();

  5.  
  6. private static Lock lock = new ZookeeperLock1("localhost", 3000, "/node");

  7.  
  8. public static void main(String[] args) throws Exception{

  9.  
  10. for(int i = 0; i < 10; i++){

  11. new Thread(){

  12. public void run(){

  13. // Lock lock = new ZookeeperLock1("localhost", 3000, "/node");

  14. try{

  15. lock.lock();

  16. System.out.println(Thread.currentThread().getName() + "开始执行");

  17. try {

  18. Thread.sleep(100);

  19. } catch (InterruptedException e) {

  20. }

  21. System.out.println(Thread.currentThread().getName() + "执行完成 ");

  22. }

  23. finally{

  24. lock.unlock();

  25. }

  26. }

  27. }.start();

  28. }

  29. }

  30. }

测试结果:

 
  1. WatchedEvent state:SyncConnected type:None path:null

  2. Thread-5 需要等待节点删除/node/0000000021

  3. Thread-9 需要等待节点删除/node/0000000021

  4. Thread-1 需要等待节点删除/node/0000000021

  5. Thread-4 需要等待节点删除/node/0000000021

  6. Thread-0 需要等待节点删除/node/0000000021

  7. Thread-2 需要等待节点删除/node/0000000021

  8. Thread-3 需要等待节点删除/node/0000000021

  9. Thread-7 需要等待节点删除/node/0000000021

  10. Thread-6 lock成功

  11. Thread-6开始执行

  12. Thread-8 需要等待节点删除/node/0000000021

  13. Thread-6执行完成

  14. Thread-6 释放锁

  15. Thread-7 lock失败,进入等待

  16. Thread-3 lock失败,进入等待

  17. Thread-2 lock失败,进入等待

  18. Thread-8 lock失败,进入等待

  19. Thread-5 lock失败,进入等待

  20. Thread-0 lock失败,进入等待

  21. Thread-4 lock失败,进入等待

  22. Thread-1 lock失败,进入等待

  23. Thread-9 lock失败,进入等待

  24. main-EventThread 通知Lock等待中的线程重试加锁

  25. main-EventThread 通知Lock等待中的线程重试加锁

  26. main-EventThread 通知Lock等待中的线程重试加锁

  27. main-EventThread 通知Lock等待中的线程重试加锁

  28. main-EventThread 通知Lock等待中的线程重试加锁

  29. main-EventThread 通知Lock等待中的线程重试加锁

  30. main-EventThread 通知Lock等待中的线程重试加锁

  31. main-EventThread 通知Lock等待中的线程重试加锁

  32. main-EventThread 通知Lock等待中的线程重试加锁

  33. Thread-3 lock等待完成

  34. Thread-2 lock等待完成

  35. Thread-7 lock等待完成

  36. Thread-9 lock等待完成

  37. Thread-0 lock等待完成

  38. Thread-4 lock等待完成

  39. Thread-5 lock等待完成

  40. Thread-1 lock等待完成

  41. Thread-8 lock等待完成

  42. Thread-3 需要等待节点删除/node/0000000022

  43. Thread-2 需要等待节点删除/node/0000000022

  44. Thread-7 需要等待节点删除/node/0000000022

  45. Thread-9 需要等待节点删除/node/0000000022

  46. Thread-2 lock失败,进入等待

  47. Thread-3 lock失败,进入等待

  48. Thread-4 需要等待节点删除/node/0000000022

  49. Thread-1 需要等待节点删除/node/0000000022

  50. Thread-7 lock失败,进入等待

  51. Thread-0 需要等待节点删除/node/0000000022

  52. Thread-8 lock成功

  53. Thread-8开始执行

  54. Thread-5 需要等待节点删除/node/0000000022

  55. Thread-1 lock失败,进入等待

  56. Thread-9 lock失败,进入等待

  57. Thread-4 lock失败,进入等待

  58. Thread-0 lock失败,进入等待

  59. Thread-5 lock失败,进入等待

  60. Thread-8执行完成

  61. Thread-8 释放锁

  62. main-EventThread 通知Lock等待中的线程重试加锁

  63. main-EventThread 通知Lock等待中的线程重试加锁

  64. main-EventThread 通知Lock等待中的线程重试加锁

  65. main-EventThread 通知Lock等待中的线程重试加锁

  66. main-EventThread 通知Lock等待中的线程重试加锁

  67. main-EventThread 通知Lock等待中的线程重试加锁

  68. main-EventThread 通知Lock等待中的线程重试加锁

  69. main-EventThread 通知Lock等待中的线程重试加锁

  70. Thread-7 lock等待完成

  71. Thread-3 lock等待完成

  72. Thread-2 lock等待完成

  73. Thread-1 lock等待完成

  74. Thread-0 lock等待完成

  75. Thread-4 lock等待完成

  76. Thread-9 lock等待完成

  77. Thread-5 lock等待完成

  78. Thread-7 需要等待节点删除/node/0000000023

  79. Thread-3 lock成功

  80. Thread-3开始执行

  81. Thread-2 需要等待节点删除/node/0000000023

  82. Thread-7 lock失败,进入等待

  83. Thread-0 需要等待节点删除/node/0000000023

  84. Thread-4 需要等待节点删除/node/0000000023

  85. Thread-9 需要等待节点删除/node/0000000023

  86. Thread-1 需要等待节点删除/node/0000000023

  87. Thread-2 lock失败,进入等待

  88. Thread-5 需要等待节点删除/node/0000000023

  89. Thread-0 lock失败,进入等待

  90. Thread-4 lock失败,进入等待

  91. Thread-9 lock失败,进入等待

  92. Thread-1 lock失败,进入等待

  93. Thread-5 lock失败,进入等待

  94. Thread-3执行完成

  95. Thread-3 释放锁

  96. main-EventThread 通知Lock等待中的线程重试加锁

  97. main-EventThread 通知Lock等待中的线程重试加锁

  98. main-EventThread 通知Lock等待中的线程重试加锁

  99. main-EventThread 通知Lock等待中的线程重试加锁

  100. main-EventThread 通知Lock等待中的线程重试加锁

  101. main-EventThread 通知Lock等待中的线程重试加锁

  102. Thread-4 lock等待完成

  103. Thread-5 lock等待完成

  104. Thread-9 lock等待完成

  105. Thread-2 lock等待完成

  106. Thread-7 lock等待完成

  107. main-EventThread 通知Lock等待中的线程重试加锁

  108. Thread-1 lock等待完成

  109. Thread-0 lock等待完成

  110. Thread-4 需要等待节点删除/node/0000000024

  111. Thread-5 需要等待节点删除/node/0000000024

  112. Thread-9 需要等待节点删除/node/0000000024

  113. Thread-2 需要等待节点删除/node/0000000024

  114. Thread-7 需要等待节点删除/node/0000000024

  115. Thread-0 需要等待节点删除/node/0000000024

  116. Thread-4 lock失败,进入等待

  117. Thread-1 lock成功

  118. Thread-1开始执行

  119. Thread-5 lock失败,进入等待

  120. Thread-9 lock失败,进入等待

  121. Thread-2 lock失败,进入等待

  122. Thread-7 lock失败,进入等待

  123. Thread-0 lock失败,进入等待

  124. Thread-1执行完成

  125. Thread-1 释放锁

  126. main-EventThread 通知Lock等待中的线程重试加锁

  127. main-EventThread 通知Lock等待中的线程重试加锁

  128. main-EventThread 通知Lock等待中的线程重试加锁

  129. main-EventThread 通知Lock等待中的线程重试加锁

  130. main-EventThread 通知Lock等待中的线程重试加锁

  131. main-EventThread 通知Lock等待中的线程重试加锁

  132. Thread-2 lock等待完成

  133. Thread-7 lock等待完成

  134. Thread-5 lock等待完成

  135. Thread-4 lock等待完成

  136. Thread-9 lock等待完成

  137. Thread-0 lock等待完成

  138. Thread-2 需要等待节点删除/node/0000000025

  139. Thread-7 lock成功

  140. Thread-7开始执行

  141. Thread-0 需要等待节点删除/node/0000000025

  142. Thread-2 lock失败,进入等待

  143. Thread-5 需要等待节点删除/node/0000000025

  144. Thread-4 需要等待节点删除/node/0000000025

  145. Thread-9 需要等待节点删除/node/0000000025

  146. Thread-0 lock失败,进入等待

  147. Thread-5 lock失败,进入等待

  148. Thread-4 lock失败,进入等待

  149. Thread-9 lock失败,进入等待

  150. Thread-7执行完成

  151. Thread-7 释放锁

  152. main-EventThread 通知Lock等待中的线程重试加锁

  153. main-EventThread 通知Lock等待中的线程重试加锁

  154. main-EventThread 通知Lock等待中的线程重试加锁

  155. main-EventThread 通知Lock等待中的线程重试加锁

  156. main-EventThread 通知Lock等待中的线程重试加锁

  157. Thread-2 lock等待完成

  158. Thread-0 lock等待完成

  159. Thread-9 lock等待完成

  160. Thread-4 lock等待完成

  161. Thread-5 lock等待完成

  162. Thread-2 需要等待节点删除/node/0000000026

  163. Thread-0 lock成功

  164. Thread-0开始执行

  165. Thread-9 需要等待节点删除/node/0000000026

  166. Thread-4 需要等待节点删除/node/0000000026

  167. Thread-5 需要等待节点删除/node/0000000026

  168. Thread-2 lock失败,进入等待

  169. Thread-9 lock失败,进入等待

  170. Thread-4 lock失败,进入等待

  171. Thread-5 lock失败,进入等待

  172. Thread-0执行完成

  173. Thread-0 释放锁

  174. main-EventThread 通知Lock等待中的线程重试加锁

  175. main-EventThread 通知Lock等待中的线程重试加锁

  176. main-EventThread 通知Lock等待中的线程重试加锁

  177. main-EventThread 通知Lock等待中的线程重试加锁

  178. Thread-2 lock等待完成

  179. Thread-4 lock等待完成

  180. Thread-5 lock等待完成

  181. Thread-9 lock等待完成

  182. Thread-2 lock成功

  183. Thread-2开始执行

  184. Thread-4 需要等待节点删除/node/0000000027

  185. Thread-5 需要等待节点删除/node/0000000027

  186. Thread-9 需要等待节点删除/node/0000000027

  187. Thread-4 lock失败,进入等待

  188. Thread-5 lock失败,进入等待

  189. Thread-9 lock失败,进入等待

  190. Thread-2执行完成

  191. Thread-2 释放锁

  192. main-EventThread 通知Lock等待中的线程重试加锁

  193. main-EventThread 通知Lock等待中的线程重试加锁

  194. main-EventThread 通知Lock等待中的线程重试加锁

  195. Thread-9 lock等待完成

  196. Thread-5 lock等待完成

  197. Thread-4 lock等待完成

  198. Thread-9 需要等待节点删除/node/0000000028

  199. Thread-5 需要等待节点删除/node/0000000028

  200. Thread-4 lock成功

  201. Thread-4开始执行

  202. Thread-9 lock失败,进入等待

  203. Thread-5 lock失败,进入等待

  204. Thread-4执行完成

  205. Thread-4 释放锁

  206. main-EventThread 通知Lock等待中的线程重试加锁

  207. main-EventThread 通知Lock等待中的线程重试加锁

  208. Thread-9 lock等待完成

  209. Thread-5 lock等待完成

  210. Thread-9 lock成功

  211. Thread-9开始执行

  212. Thread-5 需要等待节点删除/node/0000000029

  213. Thread-5 lock失败,进入等待

  214. Thread-9执行完成

  215. Thread-9 释放锁

  216. main-EventThread 通知Lock等待中的线程重试加锁

  217. Thread-5 lock等待完成

  218. Thread-5 lock成功

  219. Thread-5开始执行

  220. Thread-5执行完成

  221. Thread-5 释放锁

最近wait/notify机制好像是被什么机制替代了呢,pack/upack?忘了。

转载地址:https://blog.csdn.net/naruto_mr/article/details/81506065

猜你喜欢

转载自blog.csdn.net/u010074988/article/details/88249265