1. 概述

本文将通过几个实际例子,讲解如何在 Java 中实现概率控制与随机事件模拟。无论是做 A/B 测试、限流降级,还是模拟真实世界的统计分布,掌握概率编程都能让你的系统更智能、更贴近现实。

我们不会停留在“生成随机数”这种基础操作,而是深入到如何控制事件发生的概率,以及如何用数学分布建模真实场景。踩过坑的都知道,简单粗暴地 Math.random() 一把梭,后期很难收敛和验证。

2. 基础概率的模拟

要模拟概率,核心是生成高质量的随机数。Java 提供了多种方式,但推荐使用 SplittableRandom,它在多线程环境下性能更好,随机性也更强 ✅。

SplittableRandom random = new SplittableRandom();

接下来,我们通过控制随机数的取值范围,来实现指定概率的事件触发。

比如,想模拟一个 10% 概率发生的事件:

boolean probablyFalse = random.nextInt(10) == 0;

✅ 解析:nextInt(10) 生成 [0, 9] 的整数,共 10 个可能值,== 0 的概率就是 1/10 = 10%。

再比如,模拟 50% 概率的抛硬币场景:

boolean whoKnows = random.nextInt(1, 101) <= 50;

✅ 解析:nextInt(1, 101) 生成 [1, 100] 的整数,≤50 的情况有 50 种,正好占一半。

⚠️ 踩坑提示:注意 nextInt(a, b) 是左闭右开区间 [a, b),别被边界坑了。

3. 均匀分布(Uniform Distribution)

前面的例子都属于均匀分布——每个结果出现的概率相等,就像掷一个公平的骰子。

这种分布适合模拟游戏逻辑、抽奖、小概率功能灰度发布等场景。

3.1. 按指定概率执行函数

假设你在做一个电商系统,想给 10% 的用户随机发优惠券。我们可以封装一个通用方法,根据概率决定执行哪个逻辑分支。

先用 Vavr 的 Lazy 延迟初始化随机生成器,避免重复创建:

private final Lazy<SplittableRandom> random = Lazy.of(SplittableRandom::new);

然后实现核心方法:

public <T> T withProbability(Supplier<T> positiveCase, Supplier<T> negativeCase, int probability) {
    SplittableRandom random = this.random.get();
    if (random.nextInt(1, 101) <= probability) {
        return positiveCase.get();
    } else {
        return negativeCase.get();
    }
}

✅ 使用示例:

String result = withProbability(
    () -> "获得优惠券",
    () -> "未中奖",
    10  // 10% 概率
);

简单粗暴,清晰明了,适合在接口限流、AB 实验中使用。

3.2. 用蒙特卡洛方法估算概率

有时候我们不知道某个事件的真实概率,或者计算太复杂。这时候可以用蒙特卡洛方法(Monte Carlo Method)——通过大量随机试验来逼近真实概率。

📌 核心思想:

多次重复实验,统计成功次数,用频率近似概率。

比如,我们想验证上面 withProbability(10%) 是否真的接近 10%,可以模拟 100 万次:

int numberOfSamples = 1_000_000;
int probability = 10;

int howManyTimesInvoked = 
  Stream.generate(() -> randomInvoker.withProbability(() -> 1, () -> 0, probability))
    .limit(numberOfSamples)
    .mapToInt(Integer::intValue)
    .sum();

然后计算频率:

int monteCarloProbability = (howManyTimesInvoked * 100) / numberOfSamples;

✅ 预期结果:monteCarloProbability 应该非常接近 10

⚠️ 注意:样本量越大,结果越精确。小样本容易出现偏差(比如抽 10 次全中,概率变成 100% 😅)。

4. 其他分布:正态分布(Normal Distribution)

现实世界中,很多现象并不服从均匀分布。比如:

  • 人的身高
  • 考试成绩
  • 用户响应时间

这些通常符合正态分布(Normal Distribution)——中间多、两头少,呈钟形曲线。

如果你还在用 random.nextInt(150, 200) 生成“随机身高”,那模型就太粗糙了 ❌。真实人群中,175cm 左右的人最多,150cm 或 190cm 的极少。

4.1. 使用 Apache Commons Math

别自己实现数学模型!推荐使用 Apache Commons Math,它提供了多种分布的实现。

添加依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-math3</artifactId>
    <version>3.6.1</version>
</dependency>

配置正态分布(以中国男性平均身高为例):

private static final double MEAN_HEIGHT = 176.02;        // 均值(cm)
private static final double STANDARD_DEVIATION = 7.11;   // 标准差(cm)
private static final NormalDistribution distribution = new NormalDistribution(MEAN_HEIGHT, STANDARD_DEVIATION);

生成一个符合分布的随机身高:

public static double generateNormalHeight() {
    return distribution.sample();
}

✅ 示例输出可能为:174.3, 180.1, 176.0 —— 更贴近现实。

4.2. 反向查询:计算某区间的概率

更强大的是,我们可以反向查询:某人身高在 170cm 到 180cm 之间的概率是多少?

public static double probabilityOfHeightBetween(double heightLowerExclusive, double heightUpperInclusive) {
    return distribution.probability(heightLowerExclusive, heightUpperInclusive);
}

✅ 使用示例:

double p = probabilityOfHeightBetween(170, 180);
System.out.println("身高在 170~180cm 的概率: " + String.format("%.2f%%", p * 100));
// 输出:身高在 170~180cm 的概率: 58.87%

这个能力在风控、用户画像、A/B 实验分析中非常实用。

5. 总结

本文带你从零实现 Java 中的概率控制,重点包括:

  • ✅ 使用 SplittableRandom 替代 Math.random(),性能更好
  • ✅ 通过范围比较实现指定概率事件,如 10% 抽奖
  • ✅ 封装 withProbability 方法,让概率控制更优雅
  • ✅ 用蒙特卡洛方法验证概率模型,避免“我以为是 10%,其实是 30%”
  • ✅ 引入正态分布处理现实场景,告别“均匀随机”的粗糙建模
  • ✅ 使用 Apache Commons Math 快速实现复杂分布,不重复造轮子

记住:概率不是玄学,而是可计算、可验证的工程能力。下次做灰度发布、AB 实验或模拟系统行为时,不妨试试这些方法,让你的代码更有“人味儿”。


原始标题:Probability in Java | Baeldung