【037】面试必问!ThreadLocalRandom 和 SecureRandom 选不对,项目直接踩雷!

在这里插入图片描述

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌

📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌

零、引入

“完了完了!用户验证码被破解,账户里的钱被转走了!” 王二瘫在工位上,脸色惨白 —— 他图省事用 ThreadLocalRandom 生成支付验证码,结果黑客轻松破解,公司赔了用户好几万,领导的怒火能把天花板烧穿。隔壁哇哥掐灭烟头,一把薅过王二的代码:“早告诉你加密场景不能用 ThreadLocalRandom!SecureRandom 才是安全的,今天我把这俩的区别掰开揉碎讲,再画清楚对比图,下次再用错,你直接卷铺盖走人!”

在这里插入图片描述

点赞 + 关注,跟着哇哥和王二,吃透 ThreadLocalRandom 和 SecureRandom 的核心区别,选对工具,避免项目踩雷,面试也能稳拿分!

一、王二的致命坑:用 ThreadLocalRandom 生成支付验证码

在这里插入图片描述
王二的支付系统里,验证码生成逻辑就一行代码,看着简单,却埋了个炸雷:

// 王二的坑代码:用ThreadLocalRandom生成6位支付验证码
public static String generatePayCode() {
    return String.format("%06d", ThreadLocalRandom.current().nextInt(1000000));
}

上线没几天,就有用户反馈 “验证码刚收到,就有人用验证码登录转走了钱”。安全组一查,黑客通过分析验证码的规律,轻松预测出后续的验证码 ——ThreadLocalRandom 生成的随机数,在黑客眼里就是 “明牌”。

“你这是拿玩具枪去抢银行!” 哇哥指着代码骂,“ThreadLocalRandom 是给高并发场景用的‘普通随机数’,SecureRandom 才是加密级的‘安全随机数’,两者的安全级别差着十万八千里!”

二、用 “彩票摇号 vs 超市抽奖” 讲透核心区别

哇哥拿生活里的例子一对比,王二瞬间懂了:

  • ThreadLocalRandom:像超市的抽奖机,随机数是 “伪随机”,基于固定算法和种子生成,知道种子就能算出所有结果(比如超市员工知道抽奖机的算法,能提前算出中奖号码);

  • SecureRandom:像彩票中心的摇号机,随机数是 “强伪随机 / 真随机”,基于系统的熵池(鼠标移动、键盘输入、网络延迟等不可预测的物理数据)生成,根本无法预测。

✅ 详细对比

*** 随机数类型**
在这里插入图片描述

  • 安全性

在这里插入图片描述

  • 底层原理

在这里插入图片描述

  • 性能(生成10万随机数)
    在这里插入图片描述
  • 使用场景

在这里插入图片描述

✔️ 为什么 SecureRandom 更安全?核心就 2 点

在这里插入图片描述

📌 种子来源不可预测:

  • ThreadLocalRandom 的种子来自 “系统时间 + 线程探针”,是可复现的(比如知道生成时间,就能反推种子);

  • SecureRandom 的种子来自系统熵池 —— 收集鼠标移动、键盘敲击、磁盘 IO 延迟、网络数据包到达时间等 “无规律的物理数据”,这些数据完全不可预测,就算黑客拿到部分数据,也推不出种子。

➡️ 算法符合密码学标准:

  • ThreadLocalRandom 用的是普通的伪随机算法(比如线性移位反馈),生成的随机数有规律;
  • SecureRandom 用的是密码学安全的算法(比如 SHA1PRNG、CTR_DRBG),生成的随机数分布均匀、无规律,符合《密码学随机数生成器标准》。

“简单说,ThreadLocalRandom 是‘按公式算数字’,SecureRandom 是‘抓一把沙子数颗粒’,” 哇哥总结,“公式能破解,沙子的数量永远算不准!”

“简单说,ThreadLocalRandom 是‘按公式算数字’,SecureRandom 是‘抓一把沙子数颗粒’,” 哇哥总结,“公式能破解,沙子的数量永远算不准!”

三、性能实测:两者差多少?(附完整代码)

王二不信 “性能差很多”,哇哥直接写了个性能测试代码,实测生成 10 万随机数的耗时,结果让王二目瞪口呆。

📢 性能对比代码(完整可运行)

package cn.tcmeta.randoms;


import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

/**
 * @author: laoren
 * @description: // ThreadLocalRandom vs SecureRandom 性能对比
 * @version: 1.0.0
 */
public class RandomPerformanceTest {
    // 测试次数:生成100万随机数
    private static final int TEST_COUNT = 1000000;

    // 测试ThreadLocalRandom性能
    public static void testThreadLocalRandom() {
        long start = System.nanoTime();
        ThreadLocalRandom random = ThreadLocalRandom.current();
        for (int i = 0; i < TEST_COUNT; i++) {
            random.nextInt(1000000); // 生成0-999999的随机数
        }
        long end = System.nanoTime();
        long cost = TimeUnit.NANOSECONDS.toMillis(end - start);
        System.out.println("ThreadLocalRandom生成" + TEST_COUNT + "个随机数耗时:" + cost + "ms");
    }

    // 测试SecureRandom性能(SHA1PRNG算法)
    public static void testSecureRandom() throws NoSuchAlgorithmException {
        // 获取加密级SecureRandom实例(指定算法)
        SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
        long start = System.nanoTime();
        for (int i = 0; i < TEST_COUNT; i++) {
            secureRandom.nextInt(1000000); // 生成0-999999的随机数
        }
        long end = System.nanoTime();
        long cost = TimeUnit.NANOSECONDS.toMillis(end - start);
        System.out.println("SecureRandom(SHA1PRNG)生成" + TEST_COUNT + "个随机数耗时:" + cost + "ms");
    }

    static void main() throws NoSuchAlgorithmException {
        // 先预热JVM,避免首次执行误差
        testThreadLocalRandom();
        testSecureRandom();

        System.out.println("===== 正式测试 =====");
        testThreadLocalRandom();
        testSecureRandom();
    }
}

执行结果:

ThreadLocalRandom生成1000000个随机数耗时:6ms
SecureRandom(SHA1PRNG)生成1000000个随机数耗时:82ms
===== 正式测试 =====
ThreadLocalRandom生成1000000个随机数耗时:4ms
SecureRandom(SHA1PRNG)生成1000000个随机数耗时:52ms

结果分析

  • ThreadLocalRandom:生成 100 万随机数几乎 “零耗时”(实际约 10ms 内),无锁竞争,性能拉满;
  • SecureRandom:耗时约 50ms,是 ThreadLocalRandom 的 50 倍左右;如果系统熵池不足(比如服务器无鼠标 / 键盘输入),耗时会涨到 1 秒以上。

“性能差这么多,为啥还要用 SecureRandom?” 王二问。

“安全和性能永远是取舍,” 哇哥说,“生成验证码时,慢 500ms 但能保证用户资金安全;生成订单号时,快 10ms 能提升 QPS,场景不同,选择不同!”

四、场景决策:该用哪个?

在这里插入图片描述

👉 决策图

为了让王二不再用错,哇哥画了一张 “场景决策图”,照着选绝对不会错。

在这里插入图片描述

🔥 实战示例:正确用法(直接抄)

‼️ 示例 1:SecureRandom 生成支付验证码(加密场景)

package cn.tcmeta.randoms;


import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;

/**
 * @author: laoren
 * @description: 正确:SecureRandom生成支付验证码
 * @version: 1.0.0
 */
public class SecurePayCodeGeneratorSample {
    // 全局单例(避免频繁初始化,提升性能)
    private static final SecureRandom SECURE_RANDOM;


    static {
        try {
            // 获取加密级实例(指定算法,避免依赖系统默认)
            SECURE_RANDOM = SecureRandom.getInstance("SHA1PRNG");
            // 强制刷新种子(从熵池重新获取,提升安全性)
            SECURE_RANDOM.nextBytes(new byte[16]);
        } catch (NoSuchAlgorithmException e) {
            // 降级处理:使用系统默认算法
            throw new RuntimeException("初始化SecureRandom失败", e);
        }
    }

    // 生成6位支付验证码
    public static String generatePayCode() {
        // 生成0-999999的随机数,补0成6位
        int code = SECURE_RANDOM.nextInt(1000000);
        return String.format("%06d", code);
    }

    static void main() {
        // 测试生成验证码
        for (int i = 0; i < 5; i++) {
            System.out.println("支付验证码:" + generatePayCode());
        }
    }
}

在这里插入图片描述

😭 示例 2:ThreadLocalRandom 生成订单号(高并发非加密场景)

package cn.tcmeta.randoms;


import java.util.concurrent.ThreadLocalRandom;

/**
 * @author: laoren
 * @description: // 正确:ThreadLocalRandom生成订单号
 * @version: 1.0.0
 */
public class OrderNoGeneratorSample {

    // 生成订单号:时间戳+8位随机数
    public static String generateOrderNo() {
        long timestamp = System.currentTimeMillis();
        // ThreadLocalRandom生成8位随机数
        int randomNum = ThreadLocalRandom.current().nextInt(100000000);
        return timestamp + String.format("%08d", randomNum);
    }

    static void main() {
        // 测试生成订单号
        for (int i = 0; i < 5; i++) {
            System.out.println("订单号:" + generateOrderNo());
        }
    }
}

在这里插入图片描述

五、避坑指南:90% 的人踩过的 2 个坑

❌ 坑 1:频繁 new SecureRandom 实例(性能雪上加霜)

// 错误:每次生成验证码都new SecureRandom,熵池频繁刷新,耗时翻倍
public static String wrongGenerateCode() {
    SecureRandom random = new SecureRandom(); // 频繁new
    return String.format("%06d", random.nextInt(1000000));
}

正确做法:全局单例,初始化一次即可(参考上面的 SecurePayCodeGenerator)。

❌ 坑 2:用 SecureRandom 生成大量非加密随机数(拖累性能)

// 错误:生成订单号用SecureRandom,QPS暴跌
public static String wrongGenerateOrderNo() {
    SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
    return System.currentTimeMillis() + String.format("%08d", random.nextInt(100000000));
}

正确做法:非加密场景用 ThreadLocalRandom,把性能留给核心加密逻辑。

六、总结:核心心法(王二记满小本本)

在这里插入图片描述

📒 至强总结

  • 安全优先选 SecureRandom:凡是和 “钱、密码、token、密钥” 相关的,必须用 SecureRandom,慢一点也值得;
  • 性能优先选 ThreadLocalRandom:高并发下的普通随机数场景(订单号、随机延时),用 ThreadLocalRandom,QPS 直接起飞;
  • 单线程随便选:低并发场景,Random 和 ThreadLocalRandom 性能差不多,怎么顺手怎么写;
  • SecureRandom 优化技巧:全局单例 + 提前刷新种子,能提升 50% 以上性能。

🥚哇哥的面试彩蛋

“面试时被问这俩的区别,你先画对比图,再讲彩票摇号的例子,最后说性能实测数据,” 哇哥传授技巧,“面试官一看你既懂原理、又测过性能、还知道避坑,绝对给你打高分!记住:技术选型不是选最好的,而是选最适合场景的。”

在这里插入图片描述
如果对你有帮助, 一键三连关注我.
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值