【018】‼️大促系统崩了后,Phaser 教我给动态线程 “按阶段打卡”!

请添加图片描述

零、引入

请添加图片描述
你接手公司 618 大促的 “分阶段任务系统”,拍胸脯跟领导说 “用 CyclicBarrier 稳如老狗”—— 结果上线前 1 小时,运营临时加了 3 个秒杀商品线程,你改完代码一跑,直接乱套:预热阶段还没结束,下单线程就提前冲出去了,结算阶段少算 200 笔订单,服务器日志刷满 “阶段同步失败”,领导把你叫到会议室,拍桌的声音比 JVM GC 还响:“1 小时内修复,不然 618 你就在公司打地铺!”

你慌得满头汗 —— 之前用的 CyclicBarrier 初始化时定了 8 个线程,临时加 3 个变成 11 个,计数对不上,同步直接失效;换成 CountDownLatch 吧,又只能用一次,分阶段的预热、下单、结算根本搞不定。就在你对着代码想把 “CyclicBarrier” 改成 “CountDownLatch” 碰碰运气时,王哥叼着冰棒凑过来:“慌啥?动态线程分阶段同步,用 Phaser 啊!这玩意儿是线程界的‘智能打卡机’,能加人、能减人、还能按阶段打卡,比你这俩老古董灵活 10 倍!”

一、先吐槽旧工具:CountDownLatch/CyclicBarrier=“固定座位的公交车”

请添加图片描述
王哥舔了口冰棒,指着你的代码笑出抬头纹:“你用 CyclicBarrier 处理动态线程,就像给公交车定了 8 个座位,结果上来 11 个人,要么挤不上,要么有人站着没打卡 —— 同步直接失效!CountDownLatch 更惨,是‘一次性公交车’,坐一趟就报废,分阶段任务根本没法用。”

1.1 你的错误代码(CyclicBarrier 固定计数版)

import java.util.concurrent.CyclicBarrier;

public class BadPromotionDemo {
    // 初始化定死8个线程,临时加3个就废了
    static CyclicBarrier barrier = new CyclicBarrier(8, () -> {
        System.out.println("阶段完成!进入下一个阶段~");
    });

    public static void main(String[] args) {
        // 初始8个商品线程
        for (int i = 1; i <= 8; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + ":预热阶段完成,等所有人打卡");
                    barrier.await(); // 预热阶段打卡

                    System.out.println(Thread.currentThread().getName() + ":下单阶段完成,等所有人打卡");
                    barrier.await(); // 下单阶段打卡

                    System.out.println(Thread.currentThread().getName() + ":结算阶段完成,等所有人打卡");
                    barrier.await(); // 结算阶段打卡
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "商品线程" + i).start();
        }

        // 运营临时加3个秒杀线程(问题核心:计数不对,同步失效)
        for (int i = 9; i <= 11; i++) {
            new Thread(() -> {
                try {
                    System.out.println(Thread.currentThread().getName() + ":预热阶段完成,等所有人打卡");
                    barrier.await(); // 这3个线程会让整体计数永远凑不齐8,卡死!
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }, "秒杀线程" + i).start();
        }
    }
}

跑起来直接卡死 ——8 个初始线程等 3 个秒杀线程,3 个秒杀线程等 8 个初始线程凑 8 个,互相等,CPU 直接空转,气得你想砸键盘。

插个冷笑话:“CountDownLatch 是‘一次性筷子’,用一次就扔;CyclicBarrier 是‘固定长度的可降解筷子’,能重复用但尺寸改不了;
Phaser 是‘伸缩筷’,想长就长想短就短,还能分阶段用 —— 程序员选工具,就像干饭选筷子,得选适配的!”

二、Phaser:动态线程的 “智能打卡机”

请添加图片描述

王哥把冰棒棍扔垃圾桶,打开 IDE:“Phaser 的核心是‘动态注册 + 分阶段同步’—— 线程想加入就 register (),想退出就 deregister (),每个阶段等所有注册线程打卡完,再进下一个阶段,完全不用提前定死线程数!”

2.1 先搞懂 Phaser 的核心规则(记牢!)

  • 动态注册线程:线程调用phaser.register()就能加入 “打卡群”,数量随便加,不用初始化定死;
  • 分阶段打卡:线程完成一个阶段,调用arriveAndAwaitAdvance()—— 意思是 “我打完卡了,等所有人打完再进下阶段”;
  • 动态注销线程:线程不想玩了,调用arriveAndDeregister()—— 打完卡直接退群,不影响后续阶段;
  • 阶段回调:重写onAdvance()方法,能在阶段完成时执行自定义逻辑(比如 “阶段完成,开始下阶段”);
  • 可重复用:阶段可以无限循环,不像 CountDownLatch 一次性,也不像 CyclicBarrier 计数固定。

生活场景比喻:

“Phaser 就像公司的 618 加班打卡:

  1. 预热阶段:所有人到公司打卡,少一个都不开会(arriveAndAwaitAdvance);
  2. 临时加 3 个员工(register),打卡机自动加名额,不用改配置;
  3. 有员工提前走(deregister),打卡机自动减名额;
  4. 每个阶段打完卡,老板喊 “下阶段开始”(onAdvance);
    全程不用提前定死人数,动态调整,比 CyclicBarrier 灵活 10 倍!”
import java.util.concurrent.Phaser;

// 自定义Phaser,重写onAdvance实现阶段回调
class PromotionPhaser extends Phaser {
    // phase:当前阶段数(从0开始),registeredParties:当前注册的线程数
    @Override
    protected boolean onAdvance(int phase, int registeredParties) {
        switch (phase) {
            case 0:
                System.out.println("===== 预热阶段完成!共" + registeredParties + "个线程参与 =====");
                return false; // false表示继续下阶段,true表示Phaser终止
            case 1:
                System.out.println("===== 下单阶段完成!共" + registeredParties + "个线程参与 =====");
                return false;
            case 2:
                System.out.println("===== 结算阶段完成!共" + registeredParties + "个线程参与 =====");
                return true; // 所有阶段完成,终止Phaser
            default:
                return true;
        }
    }
}

public class GoodPromotionDemo {
    public static void main(String[] args) {
        // 初始化Phaser,初始注册线程数0(动态加!)
        PromotionPhaser phaser = new PromotionPhaser();

        // 初始8个商品线程
        for (int i = 1; i <= 8; i++) {
            new Thread(() -> {
                phaser.register(); // 注册线程,加入打卡群
                try {
                    System.out.println(Thread.currentThread().getName() + ":预热阶段完成,打卡等所有人");
                    phaser.arriveAndAwaitAdvance(); // 预热阶段打卡

                    System.out.println(Thread.currentThread().getName() + ":下单阶段完成,打卡等所有人");
                    phaser.arriveAndAwaitAdvance(); // 下单阶段打卡

                    System.out.println(Thread.currentThread().getName() + ":结算阶段完成,打卡等所有人");
                    phaser.arriveAndAwaitAdvance(); // 结算阶段打卡
                } finally {
                    phaser.arriveAndDeregister(); // 完成所有阶段,注销退出
                }
            }, "商品线程" + i).start();
        }

        // 运营临时加3个秒杀线程(动态注册,完美适配)
        for (int i = 9; i <= 11; i++) {
            new Thread(() -> {
                phaser.register(); // 动态注册,打卡群自动加1
                try {
                    System.out.println(Thread.currentThread().getName() + ":预热阶段完成,打卡等所有人");
                    phaser.arriveAndAwaitAdvance(); // 预热打卡

                    System.out.println(Thread.currentThread().getName() + ":秒杀商品卖完,提前退出");
                    phaser.arriveAndDeregister(); // 下单/结算阶段不参与,注销退出
                } finally {
                    // 防止线程异常导致没注销
                    if (!phaser.isTerminated()) {
                        phaser.arriveAndDeregister();
                    }
                }
            }, "秒杀线程" + i).start();
        }
    }
}

运行结果(动态同步,丝滑无比):

商品线程1:预热阶段完成,打卡等所有人
...(所有11个线程都打印预热完成)
===== 预热阶段完成!共11个线程参与 =====
商品线程1:下单阶段完成,打卡等所有人
...(8个商品线程打印下单完成,3个秒杀线程已退出)
===== 下单阶段完成!共8个线程参与 =====
商品线程1:结算阶段完成,打卡等所有人
...(8个商品线程打印结算完成)
===== 结算阶段完成!共8个线程参与 =====

“你看,临时加的 3 个秒杀线程完美融入,卖完货还能提前退出,不影响后续阶段!” 王哥拍桌,“Phaser 帮你把‘动态注册、阶段同步、注销退出’全做了,不用写一行锁和通知逻辑,比 CyclicBarrier 省心 10 倍!”

三、Phaser 核心方法拆解(看完就会用)

3.1 核心方法拆解

王哥怕你记不住,把 Phaser 的核心方法拆成 “人话版”,比背单词还简单:

在这里插入图片描述

3.2 进阶示例:动态加减线程(线上环境必用)

“线上环境线程可能异常退出,得能动态减线程;也可能临时加任务,得能动态加线程。” 王哥补了个更贴近实战的代码:

package cn.tcmeta.phaser;

import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;

/**
 * @author: laoren
 * @date: 2025/12/4 14:22
 * @description: 动态加减线程(线上环境必用)
 * @version: 1.0.0
 */
public class PhaserDynamicSample {
    public static void main(String[] args) {
        Phaser phaser = new Phaser() {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("阶段" + phase + "完成,当前注册线程数:" + registeredParties);
                return phase >= 2; // 阶段0、1、2完成后终止
            }
        };

        // 初始两个线程预先注册
        phaser.register();
        phaser.register();

        Thread t1 = new Thread(createTask(phaser, "线程 - 1"), "线程 - 1");
        Thread t2 = new Thread(createTask(phaser, "线程 - 2"), "线程 - 2");

        t1.start();
        t2.start();

        try {
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt(); // 正确恢复中断标志位
            e.printStackTrace();
        }

        new Thread(() -> {
            phaser.register(); // 注册
            int currentPhase = phaser.getPhase();

            // 只有在Phaser未终止时才继续执行
            while (currentPhase >= 0 && currentPhase < 3) {
                System.out.println(Thread.currentThread().getName() + ":阶段" + currentPhase + "完成");
                phaser.arriveAndAwaitAdvance();
                currentPhase = phaser.getPhase();
            }

            // 如果Phaser已终止,则直接注销
            if (currentPhase < 0) {
                phaser.arriveAndDeregister();
            }
        }, "动态线程 - 3").start();

    }

    private static Runnable createTask(Phaser phaser, String threadName) {
        return () -> {
            for (int phase = 0; phase < 3; phase++) {
                System.out.printf("线程名称: 【%s】 , msg: %s \n", threadName, " 阶段 - " + phase + " 完成!");
                if (phase == 1 && "线程 - 1".equals(threadName)) {
                    System.out.printf("线程名称: 【%s】 , msg: %s \n", threadName, " 阶段一完成退出 ~~ ");
                    phaser.arriveAndDeregister();
                    return;
                }
                phaser.arriveAndAwaitAdvance();
            }
        };
    }
}

在这里插入图片描述

四、Phaser 的核心使用场景(看完就能落地)

王哥喝了口冰可乐,给你列了 3 个最常用的场景,每个都能直接套代码:

  • 场景 1:电商大促分阶段任务(你的核心需求)
    • 预热、下单、结算三个阶段,线程数动态变化(加秒杀线程、减售罄商品线程),用 Phaser 完美适配,不用改计数,不用重启服务。
  • 场景 2:多阶段测试用例执行比如接口测试分 “初始化、执行、清理” 三个阶段,测试线程数根据用例数动态增减,Phaser 能保证每个阶段所有用例都执行完,再进下阶段。
  • 场景 3:爬虫分阶段爬取
    • 爬虫分 “爬取网页、解析数据、入库” 三个阶段,爬取线程数根据网站反爬策略动态调整(封 IP 就减线程,解封就加),Phaser 能动态同步,不丢数据。

五、Phaser 的坑:别踩!

王哥给你划了 3 个坑,踩一个就加班:

  • 别忘注销线程:线程提前退出必须调用arriveAndDeregister(),不然注册数会一直累加,Phaser 永远不终止 —— 上次小张忘注销,注册数飙到 1000,JVM 直接 OOM;
  • onAdvance 返回值要注意:返回 true 表示 Phaser 终止,后续调用arriveAndAwaitAdvance()会直接返回,不会阻塞 —— 别想当然返回 false,不然 Phaser 永远不终止;
  • 避免重复注册:同一个线程多次调用register()会导致注册数虚高,同步失效 —— 就像一个人打卡两次,考勤人数多算一个,结果所有人都等 “不存在的人”。

彩蛋:王哥的血泪史
“我刚用 Phaser 时,把register()写在循环里,” 王哥捂脸,“一个线程注册了 10 次,导致 Phaser 一直等 10 个打卡,其他线程全卡死,我以为系统被黑客攻击了,报警后才发现是自己代码写错 —— 被领导罚抄了 10 遍 Phaser 源码!”

六、总结

  • 王哥拍了拍你的肩膀,给你总结了 “傻瓜式口诀”,记牢就能直接用:
  • 动态线程分阶段:优先用 Phaser,别用 CountDownLatch/CyclicBarrier; 注册用 register:线程加入就调,数量随便加;
  • 打卡用 arriveAndAwaitAdvance:阶段完成等所有人;
  • 退出用 arriveAndDeregister:提前走就注销,别占名额;
    阶段回调重写 onAdvance:自定义阶段逻辑,返回 true 终止 Phaser。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值