关于敏捷的一些思考(上)

最近公司为了提高协作的效率,把几十号人的科技部门拆成好几个敏捷小组,小组独立运转。然后同事们又搞了个关于敏捷的知识分享,这个分享活动上有一个很有意思的游戏,游戏的目的是为了告诉大家,怎样做才会更敏捷?为什么?

1.游戏

10个人翻30张牌,每个人要把这30张牌的每1张牌都翻1遍,计算第1个人翻完30张牌所耗费的时间和所有人翻完30张牌所耗费的时间。

image

假设每一个红色方块耗时x,每一个橙色方块耗时y,每一个黄色方块耗时z(x, y, z > 0)。

1-1.方案1

第n个人必须等第n-1个人翻完所有的30张牌才能开始翻牌,第1个人不用等。

解答:参考上图的结构,先计算第1~5个人的翻完30张牌的时间,然后再乘以2就是所有的耗时,且不存在影响时间的意外情况,那最后所耗费的时间是:

T_1 = ((5 + 1) * 5 / 2 * x + (4 + 1) * 4 / 2 * y + 25 * 5 * z) * 2 = 30x + 20y + 250z

1-2.方案2

第n个人必须等第n-1个人翻完第5张牌才能开始翻牌,第1个人不用等;且第n个人必须等第n-1个人翻完第m张牌,才能翻第m张牌,第1个人不用等。

解答:参考上图结构,先计算1 ~ 10个人翻完前5张牌的时间,然后再计算第10个人翻完6 ~ 30张牌的时间,且不存在影响时间的意外情况,那最后所耗费的时间是:

T_2 = ((5 + 1) * 5 / 2 * x + (4 + 1) * 4 / 2 * y) * 2 + 25 * z = 30x + 20y + 25z

1-3.方案3

第n个人必须等第n-1个人翻完第m张牌,才能翻第m张牌,第1个人不用等。

解答:参考上图结构,先计算1 ~ 10个人翻完第1张牌的时间,然后再计算第10个人翻完第2 ~ 5张牌的时间,最后再加上第10个人翻完第6 ~ 30张牌的时间,且不存在影响时间的意外情况,那最后所耗费的时间是:

T_3 = 10x + 4y + 25z

2.用程序来验证正确性

2-1.预期结果

假设x = y = z = 1s,那最终结果应该是:

T_1 = 30 + 20 + 250 = 300 s

T_2 = 30 + 20 + 25 = 75s

T_3 = 10 + 4 + 25 = 39 s

2-2.实际结果

现在,咱们来基于上面三种方案来编写程序:

public class AgileTest {

    private static final Logger LOGGER = LoggerFactory.getLogger(AgileTest.class);

    public static void main(String[] args) {
        long time1 = way1();
        long time2 = way2();
        long time3 = way3();
        LOGGER.info("time1: {} ms", time1);
        LOGGER.info("time2: {} ms", time2);
        LOGGER.info("time3: {} ms", time3);
    }

    /**
     * <p>方案1</p>
     * <p>第n个人必须等第n-1个人翻完所有的30张牌才能开始翻牌,第1个人不用等。</p>
     * @return 耗时 ms
     */
    public static long way1() {
        final String way = "way1";
        long startTime = System.currentTimeMillis();
        // 1.10个人
        for (int i = 1; i < 11; i++) {
            AtomicInteger atomicI = new AtomicInteger(i);
            // 2.30张牌,每个人翻完30张牌,下个人才能开始
            for (int j = 1; j < 31; j++) {
                node(way, atomicI, j);
            }
        }
        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }

    /**
     * <p>方案2</p>
     * <p>第n个人必须等第n-1个人翻完第5张牌才能开始翻牌,第1个人不用等;且第n个人必须等第n-1个人翻完第m张牌,才能翻第m张牌,第1个人不用等。</p>
     * @return 耗时 ms
     */
    public static long way2() {
        final String way = "way2";
        long startTime = System.currentTimeMillis();
        CountDownLatch latch = new CountDownLatch(10);
        Map<Integer, Integer> map = new ConcurrentHashMap<>();
        // 1.10个人
        for (int outUser = 1; outUser < 11; outUser++) {
            int outLastUser = outUser - 1;
            Integer lastOutUserNode = 0;
            // 2.每个人必须等前面那个人翻完5张牌才能开始翻牌,第1个人例外
            while (outUser != 1 && ((lastOutUserNode = map.get(outLastUser)) == null || lastOutUserNode < 5)) {
            }
            AtomicInteger atomicI = new AtomicInteger(outUser);
            Thread thread = new Thread(() -> {
                try {
                    int innerUser = atomicI.get();
                    int lastInnerUser = innerUser - 1;
                    // 3.30张牌
                    for (int j = 1; j < 31; j++) {
                        // 4.翻牌的速度不能超过前1个人,第1个人例外,第30张除外
                        Integer lastInnerUserNode = 0;
                        while (innerUser != 1 && ((lastInnerUserNode = map.get(lastInnerUser)) == null || lastInnerUserNode < j)) {
                        }
                        node(way, atomicI, j);
                        map.put(innerUser, j);
                    }
                } catch (Exception e) {
                    LOGGER.error("Current Thread: " + Thread.currentThread().getName() +  "+, exception: ", e);
                } finally {
                    latch.countDown();
                }
            }, "thread-user-" + outUser);
            thread.start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            LOGGER.error("wait thread exception: ", e);
        }

        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }

    /**
     * <p>方案3</p>
     * <p>第n个人必须等第n-1个人翻完第m张牌,才能翻第m张牌,第1个人不用等。</p>
     * @return 耗时 ms
     */
    public static long way3() {
        final String way = "way3";
        long startTime = System.currentTimeMillis();
        CountDownLatch latch = new CountDownLatch(10);
        Map<Integer, Integer> map = new ConcurrentHashMap<>();
        // 1.10个人
        for (int outUser = 1; outUser < 11; outUser++) {
            AtomicInteger atomicI = new AtomicInteger(outUser);
            Thread thread = new Thread(() -> {
                try {
                    int innerUser = atomicI.get();
                    int lastInnerUser = innerUser - 1;
                    // 2.30张牌
                    for (int j = 1; j < 31; j++) {
                        // 3.翻牌的速度不能超过前1个人,第1个人例外
                        Integer lastInnerUserNode = 0;
                        while (innerUser != 1 && ((lastInnerUserNode = map.get(lastInnerUser)) == null || lastInnerUserNode < j)) {
                        }
                        node(way, atomicI, j);
                        map.put(innerUser, j);
                    }
                } catch (Exception e) {
                    LOGGER.error("Current Thread: " + Thread.currentThread().getName() +  "+, exception: ", e);
                } finally {
                    latch.countDown();
                }
            }, "thread-user-" + outUser);
            thread.start();
        }

        try {
            latch.await();
        } catch (InterruptedException e) {
            LOGGER.error("wait thread exception: ", e);
        }

        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }

    /**
     * 执行节点
     * @param atomicI 第i个人
     * @param j 第j张牌
     */
    private static void node(String way, AtomicInteger atomicI, int j) {
        int i = atomicI.get();
        long currentTime = System.currentTimeMillis() / 1000;
//        System.out.printf("[%s] %s i-j: %d-%d %d\n", Thread.currentThread().getName(), way, i, j, currentTime);
        try {
            if ((i >= 1 && i <= 5 && j >= 1 && j <= 5 && i + j <= 6)
                    || (i >= 6 && i <= 10 && j >= 1 && j <= 5 && i + j <= 11)) {
                Thread.sleep(1000);
            } else if ((i >= 1 && i <= 5 && j >= 1 && j <= 5 && i + j > 6)
                    || (i >= 6 && i <= 10 && j >= 1 && j <= 5 && i + j > 11)) {
                Thread.sleep(1000);
            } else if (j >= 6) {
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            LOGGER.error("node sleep exception: ", e);
        }
    }
}

日志输出:

17:52:10.645 [main] INFO com.peng.java_study.practice.zhikan.AgileTest - time1: 302746 ms
17:52:10.648 [main] INFO com.peng.java_study.practice.zhikan.AgileTest - time2: 75632 ms
17:52:10.648 [main] INFO com.peng.java_study.practice.zhikan.AgileTest - time3: 39348 ms

从日志中可以看出,实际结果与预期基本一致,多出来的几百毫秒是程序在运行过程中不可避免的消耗。

2-3.小结

不考虑其他因素影响的前提下,3种方案的耗时排行:

T_3 < T_2 < T_1

从结果中确实可以看出方案3是最敏捷的,值得提倡!

3.深入思考

方案3一定会更快吗?

答案是否定的,在某些特殊情况下,方案3反而更慢!

3-1.方案1'

假设第n - 1个人翻完1 ~ 30张牌后,一起交接给第n个人的准备时间和沟通时间为a。那么最终耗时为:

T_{1}' = T_1 + (10 - 1) * a = T_1 + 9a

3-2.方案2'

假设第n - 1个人翻完第1 ~ 5张牌后,一起交接给第n个人时,需要准备时间和沟通时间为b;第n - 1个人翻完第6 ~ 30张牌中的任意一张牌后,交接给第n个人时,需要的准备时间与沟通时间c。那么最终耗时为:

T_{2}' = T_2 + (10 - 1) * b + 25 * 9 * c = T_2 + 9b + 225c

3-3.方案3'

假设第n - 1个人翻完1 ~ 30张牌中的任意一张牌后,交接给第n个人时,需要的准备时间与沟通时间为d。那么最终耗时为:

T_{3}' = T_3 + 30 * 9 * d = T_3 + 270d

3-4.比较方案1'与方案3'

当【准备时间与沟通时间】与【单个任务的执行时间时】的大小满足一定的条件时,方案3是否有可能比方案1慢。现在来计算下:

假设T_{3}' > T_{1}',那么:

T_3 + 270d > T_1 + 9a

10x + 4y + 25z + 270d > 30x + 20y + 250z + 9a

再次假设x = y = z = 1 s,那么上式可转化为:

10 + 4 + 25 + 270d > 30 + 20 + 250 + 9a

d > (261 + 9a) / 270a < (270d - 261) / 9

比如,d = 1s,那a < 1s就可以让方案1的速度比方案3更快。

3-5.小结

从3-4小结的结论中可以看出,在一定条件下,方案3可能并不是最优的,而且这种情况很有可能发生。

4.结论

可能在大多数情况下,方案3会比方案1更敏捷,但在3-4小节中的特殊情况下,方案3反而会是最慢的。因此在真实的生活场景中,还是要根据不同的情况分别对待,因地制宜,不能一概而论!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容