zoukankan      html  css  js  c++  java
  • Apache Curator之InterProcessMutex抢购案例(三)

    上一节讲了Apache Curator之分布式锁原理(二),在分析InterProcessMutex源码之前,我们先通过一个简单的手机抢购案例更深入理解分布式锁的原理。废话不多说,先上代码:

    手机实体Bean类Phone.java:很简单,只有一个number字段,模拟手机库存数量。

     1 package com.youguu.skill;
     2 
     3 public class Phone {
     4     /**
     5      * 商品库存,默认有5部手机
     6      */
     7     private static int number = 5;
     8 
     9     public static int getNumber() {
    10         return number;
    11     }
    12 
    13     public static void setNumber(int number) {
    14         Phone.number = number;
    15     }
    16 
    17 }

    用户类:

     1 package com.youguu.skill;
     2 
     3 import org.apache.curator.RetryPolicy;
     4 import org.apache.curator.framework.CuratorFramework;
     5 import org.apache.curator.framework.CuratorFrameworkFactory;
     6 import org.apache.curator.framework.recipes.locks.InterProcessMutex;
     7 import org.apache.curator.retry.ExponentialBackoffRetry;
     8 
     9 public class User {
    10 
    11     public static void main(String[] args) {
    12         //重试策略, 参数1:等待时间, 参数2:重试次数
    13         RetryPolicy policy = new ExponentialBackoffRetry(2000, 3);
    14 
    15         //创建zookeeper客户端连接
    16         CuratorFramework client = CuratorFrameworkFactory.builder().connectString("192.168.1.30:2181").retryPolicy(policy).build();
    17         client.start();
    18 
    19         //获取锁对象
    20         final InterProcessMutex mutex = new InterProcessMutex(client, "/locks");
    21 
    22         //创建10个线程,相当于10个用户去抢购5部手机
    23         for (int i = 0; i < 10; i++) {
    24             new Thread(() -> {
    25                 try {
    26                     //请求锁
    27                     mutex.acquire();
    28 
    29                     //执行抢购业务
    30                     buy();
    31                 } catch (Exception e) {
    32                     e.printStackTrace();
    33                 } finally {
    34                     try {
    35                         //释放锁
    36                         mutex.release();
    37                     } catch (Exception e) {
    38                         e.printStackTrace();
    39                     }
    40                 }
    41             }).start();
    42         }
    43     }
    44 
    45     /**
    46      * 抢购
    47      */
    48     public static void buy() {
    49         System.out.println("【" + Thread.currentThread().getName() + "】开始抢购");
    50         //获取剩余手机数量
    51         int currentNumber = Phone.getNumber();
    52 
    53         if (currentNumber == 0) {
    54             System.out.println("抢购已结束,下次再来吧");
    55         } else {
    56             System.out.println("剩余手机数量:" + currentNumber);
    57 
    58             //睡眠3秒,模拟业务逻辑处理耗时时间
    59             try {
    60                 Thread.sleep(3000);
    61             } catch (InterruptedException e) {
    62                 e.printStackTrace();
    63             }
    64 
    65             //购买后数量减1
    66             currentNumber--;
    67             Phone.setNumber(currentNumber);
    68         }
    69         System.out.println("【" + Thread.currentThread().getName() + "】  购买结束");
    70         System.out.println("-----------------------------------------");
    71     }
    72 }

    13行:配置重试策略

    16,17行:获取zookeeper连接

    20行:获取锁对象,这里相当于用户只是获得了锁对象的引用,没有执行加锁动作

    23行:创建10个线程,相当于10个用户去抢购5部手机,最后肯定是5个用户抢到手机,5个用户没有抢到。

    27行:尝试获得锁

    30行:获得锁成功,执行业务逻辑(这里只是对库存字段number减一)

    36行:释放锁,注意是在finally块里执行的。

    我们观察一下输入日志:

    【Thread-2】开始抢购
    剩余手机数量:5
    【Thread-2】  购买结束
    -----------------------------------------
    【Thread-6】开始抢购
    剩余手机数量:4
    【Thread-6】  购买结束
    -----------------------------------------
    【Thread-1】开始抢购
    剩余手机数量:3
    【Thread-1】  购买结束
    -----------------------------------------
    【Thread-3】开始抢购
    剩余手机数量:2
    【Thread-3】  购买结束
    -----------------------------------------
    【Thread-8】开始抢购
    剩余手机数量:1
    【Thread-8】  购买结束
    -----------------------------------------
    【Thread-4】开始抢购
    抢购已结束,下次再来吧
    【Thread-4】  购买结束
    -----------------------------------------
    【Thread-10】开始抢购
    抢购已结束,下次再来吧
    【Thread-10】  购买结束
    -----------------------------------------
    【Thread-7】开始抢购
    抢购已结束,下次再来吧
    【Thread-7】  购买结束
    -----------------------------------------
    【Thread-9】开始抢购
    抢购已结束,下次再来吧
    【Thread-9】  购买结束
    -----------------------------------------
    【Thread-5】开始抢购
    抢购已结束,下次再来吧
    【Thread-5】  购买结束
    -----------------------------------------

    从线程的名字可以看到,每个线程获得锁后,其他线程都是处于等待状态,直到当前线程释放锁,其它线程才能继续执行。

    从后面5个线程的输出可以看到,最后5个线程(用户)都没有抢到手机。

    所以整个输出是符合我们的预期的。

    现在我们把执行镜头放慢,看看zookeeper节点在这个过程中是怎么变化的。

    在User类30行打一个断点,此时观察zookeeper节点如下图:

    共10个path,也验证了之前所说的,每个线程在获得锁之前都会事先把临时顺序路径创建好。

    依然保持住在User类30行打的断点,我们可以观察到,已执行线程数和zookeeper剩余path数量满足如下关系:

    已执行线程数量 剩余zk path数量
    0 10
    1 9
    2 8
    3 7
    4 6
    5 5
    6 4
    7 3
    8 2
    9 1
    10 0

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    这也充分说明了,每一个线程释放锁后会删除path节点。

    观察到这里你们以为就完了?还没有,如果断点一直卡在30行,隔了一段时间我们再刷新zookeeper节点,发现locks目录下一个节点也没有了。

    但是按一下F9,再刷新zookeeper节点,发现locks目录下又有临时节点了。

    在这个图里我已经按了两下F9。可以看到还有8个节点,也就是说剩余8个线程竞争锁。关于超时删除临时节点我们下一节分析源码再说。

  • 相关阅读:
    【BZOJ 1455】罗马游戏
    【UR #2】树上GCD
    1067: [SCOI2007]降雨量
    1068: [SCOI2007]压缩
    1066: [SCOI2007]蜥蜴
    1065: [NOI2008]奥运物流
    1064: [Noi2008]假面舞会
    1063: [Noi2008]道路设计
    2329: [HNOI2011]括号修复
    2734: [HNOI2012]集合选数
  • 原文地址:https://www.cnblogs.com/shileibrave/p/9854914.html
Copyright © 2011-2022 走看看