硬核 - Java 随机数相关 API 的演进与思考(上3)

简介: 硬核 - Java 随机数相关 API 的演进与思考(上3)

Java 17 之前一般如何生成随机数以及对应的随机算法


首先放出算法与实现类的对应关系:


image.png


使用 JDK 的 API


1.使用java.util.Random和基于它的 API

Random random = new Random();
random.nextInt();

Math.random()底层也是基于 Random

java.lang.Math

public static double random() {
    return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
    static final Random randomNumberGenerator = new Random();
}

Random 本身是设计成线程安全的,因为 SEED 是 Atomic 的并且随机只是 CAS 更新这个 SEED:

java.util.Random

protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}

同时也看出,Random 是基于线性同余算法的

2.使用java.util.SplittableRandom和基于它的 API

SplittableRandom splittableRandom = new SplittableRandom();
splittableRandom.nextInt();

前面的分析我们提到了,SplittableRandom 基于 SplitMix 算法实现,即给定一个初始 SEED,设置一个固定步长 M,每次随机,将这个 SEED 加上步长 M,经过一个 HASH 函数(这里是 MurMurHash3),将这个值散列映射到一个 HASH 值。

SplittableRandom本身不是线程安全的java.util.SplittableRandom

public int nextInt() {
    return mix32(nextSeed());
}   
private long nextSeed() {
    //这里非线程安全
    return seed += gamma;
}

ThreadLocalRandom基于SplittableRandom实现,我们在多线程环境下使用 ThreadLocalRandom

ThreadLocalRandom.current().nextInt();

SplittableRandom 可以通过 split 方法返回一个参数全新,随机序列特性差异很大的新的 SplittableRandom,我们可以将他们用于不同的线程生成随机数,这在 parallel Stream 中非常常见:

IntStream.range(0, 1000)
    .parallel()
    .map(index -> usersService.getUsersByGood(index))
    .map(users -> users.get(splittableRandom.split().nextInt(users.size())))
    .collect(Collectors.toList());

但是由于没有做对齐性填充以及其他一些多线程性能优化的东西,导致其多线程环境下的性能表现还是比基于 SplittableRandomThreadLocalRandom要差。

3. 使用java.security.SecureRandom生成安全性更高的随机数

SecureRandom drbg = SecureRandom.getInstance("DRBG");
drbg.nextInt();

一般这种算法,基于加密算法实现,计算更加复杂,性能也比较差,只有安全性非常敏感的业务才会使用,一般业务(例如抽奖)这些是不会使用的。


测试性能


单线程测试:

Benchmark                                      Mode  Cnt          Score          Error  Units
TestRandom.testDRBGSecureRandomInt            thrpt   50     940907.223 ±    11505.342  ops/s
TestRandom.testDRBGSecureRandomIntWithBound   thrpt   50     992789.814 ±    71312.127  ops/s
TestRandom.testRandomInt                      thrpt   50  106491372.544 ±  8881505.674  ops/s
TestRandom.testRandomIntWithBound             thrpt   50   99009878.690 ±  9411874.862  ops/s
TestRandom.testSplittableRandomInt            thrpt   50  295631145.320 ± 82211818.950  ops/s
TestRandom.testSplittableRandomIntWithBound   thrpt   50  190550282.857 ± 17108994.427  ops/s
TestRandom.testThreadLocalRandomInt           thrpt   50  264264886.637 ± 67311258.237  ops/s
TestRandom.testThreadLocalRandomIntWithBound  thrpt   50  162884175.411 ± 12127863.560  ops/s

多线程测试:

Benchmark                                      Mode  Cnt          Score           Error  Units
TestRandom.testDRBGSecureRandomInt            thrpt   50    2492896.096 ±     19410.632  ops/s
TestRandom.testDRBGSecureRandomIntWithBound   thrpt   50    2478206.361 ±    111106.563  ops/s
TestRandom.testRandomInt                      thrpt   50  345345082.968 ±  21717020.450  ops/s
TestRandom.testRandomIntWithBound             thrpt   50  300777199.608 ±  17577234.117  ops/s
TestRandom.testSplittableRandomInt            thrpt   50  465579146.155 ±  25901118.711  ops/s
TestRandom.testSplittableRandomIntWithBound   thrpt   50  344833166.641 ±  30676425.124  ops/s
TestRandom.testThreadLocalRandomInt           thrpt   50  647483039.493 ± 120906932.951  ops/s
TestRandom.testThreadLocalRandomIntWithBound  thrpt   50  467680021.387 ±  82625535.510  ops/s

结果和我们之前说明的预期基本一致,多线程环境下 ThreadLocalRandom 的性能最好。单线程环境下 SplittableRandomThreadLocalRandom 基本接近,性能要好于其他的。SecureRandom 和其他的相比性能差了几百倍。

测试代码如下(注意虽然 Random 和 SecureRandom 都是线程安全的,但是为了避免 compareAndSet 带来的性能衰减过多,还是用了 ThreadLocal。):

package prng;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
import java.util.SplittableRandom;
import java.util.concurrent.ThreadLocalRandom;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
//测试指标为吞吐量
@BenchmarkMode(Mode.Throughput)
//需要预热,排除 jit 即时编译以及 JVM 采集各种指标带来的影响,由于我们单次循环很多次,所以预热一次就行
@Warmup(iterations = 1)
//线程个数
@Threads(10)
@Fork(1)
//测试次数,我们测试50次
@Measurement(iterations = 50)
//定义了一个类实例的生命周期,所有测试线程共享一个实例
@State(value = Scope.Benchmark)
public class TestRandom {
  ThreadLocal<Random> random = ThreadLocal.withInitial(Random::new);
  ThreadLocal<SplittableRandom> splittableRandom = ThreadLocal.withInitial(SplittableRandom::new);
  ThreadLocal<SecureRandom> drbg = ThreadLocal.withInitial(() -> {
    try {
      return SecureRandom.getInstance("DRBG");
    }
    catch (NoSuchAlgorithmException e) {
      throw new IllegalArgumentException(e);
    }
  });
  @Benchmark
  public void testRandomInt(Blackhole blackhole) throws Exception {
    blackhole.consume(random.get().nextInt());
  }
  @Benchmark
  public void testRandomIntWithBound(Blackhole blackhole) throws Exception {
    //注意不取 2^n 这种数字,因为这种数字一般不会作为实际应用的范围,但是底层针对这种数字有优化
    blackhole.consume(random.get().nextInt(1, 100));
  }
  @Benchmark
  public void testSplittableRandomInt(Blackhole blackhole) throws Exception {
    blackhole.consume(splittableRandom.get().nextInt());
  }
  @Benchmark
  public void testSplittableRandomIntWithBound(Blackhole blackhole) throws Exception {
    //注意不取 2^n 这种数字,因为这种数字一般不会作为实际应用的范围,但是底层针对这种数字有优化
    blackhole.consume(splittableRandom.get().nextInt(1, 100));
  }
  @Benchmark
  public void testThreadLocalRandomInt(Blackhole blackhole) throws Exception {
    blackhole.consume(ThreadLocalRandom.current().nextInt());
  }
  @Benchmark
  public void testThreadLocalRandomIntWithBound(Blackhole blackhole) throws Exception {
    //注意不取 2^n 这种数字,因为这种数字一般不会作为实际应用的范围,但是底层针对这种数字有优化
    blackhole.consume(ThreadLocalRandom.current().nextInt(1, 100));
  }
  @Benchmark
  public void testDRBGSecureRandomInt(Blackhole blackhole) {
    blackhole.consume(drbg.get().nextInt());
  }
  @Benchmark
  public void testDRBGSecureRandomIntWithBound(Blackhole blackhole) {
    //注意不取 2^n 这种数字,因为这种数字一般不会作为实际应用的范围,但是底层针对这种数字有优化
    blackhole.consume(drbg.get().nextInt(1, 100));
  }
  public static void main(String[] args) throws RunnerException {
    Options opt = new OptionsBuilder().include(TestRandom.class.getSimpleName()).build();
    new Runner(opt).run();
  }
}
相关文章
|
17天前
|
Java
java_键盘录入、随机数
本文介绍了Java中键盘录入和Random类的使用。键盘录入用于从用户那里获取数据,通过导入`java.util.Scanner`,创建`Scanner`对象,调用`nextInt()`或`nextDouble()`读取整数和小数,`next()`读取字符串。Random类用于生成随机整数,导入该类后创建对象,调用`nextInt(int bound)`生成[0, bound-1]范围内的随机数。在JDK17及以上版本,可以使用`nextInt(int start, int end)`生成[start, end)范围的随机数。常见应用包括猜数字游戏和随机点名。
13 0
|
1天前
|
Java API
Java基础&API(3)
Java基础&API(3)
|
1天前
|
Java 机器人 API
Java基础&常用API(1)
Java基础&常用API(1)
|
2天前
|
安全 Java 程序员
|
6天前
|
Java API Apache
ZooKeeper【基础 03】Java 客户端 Apache Curator 基础 API 使用举例(含源代码)
【4月更文挑战第11天】ZooKeeper【基础 03】Java 客户端 Apache Curator 基础 API 使用举例(含源代码)
24 11
|
7天前
|
安全 Java API
java借助代理ip,解决访问api频繁导致ip被禁的问题
java借助代理ip,解决访问api频繁导致ip被禁的问题
|
10天前
|
存储 安全 Java
说说Java 8 引入的Stream API
说说Java 8 引入的Stream API
12 0
|
10天前
|
分布式计算 Java API
Java 8新特性之Lambda表达式与Stream API
【4月更文挑战第16天】本文将介绍Java 8中的两个重要新特性:Lambda表达式和Stream API。Lambda表达式是Java 8中引入的一种新的编程语法,它允许我们将函数作为参数传递给其他方法,从而使代码更加简洁、易读。Stream API是Java 8中引入的一种新的数据处理方式,它允许我们以声明式的方式处理数据,从而使代码更加简洁、高效。本文将通过实例代码详细讲解这两个新特性的使用方法和优势。
|
11天前
|
安全 Java API
RESTful API设计与实现:Java后台开发指南
【4月更文挑战第15天】本文介绍了如何使用Java开发RESTful API,重点是Spring Boot框架和Spring MVC。遵循无状态、统一接口、资源标识和JSON数据格式的设计原则,通过创建控制器处理HTTP请求,如示例中的用户管理操作。此外,文章还提及数据绑定、验证、异常处理和跨域支持。最后,提出了版本控制、安全性、文档测试以及限流和缓存的最佳实践,以确保API的稳定、安全和高效。
|
14天前
|
存储 Java 关系型数据库
掌握Java 8 Stream API的艺术:详解流式编程(一)
掌握Java 8 Stream API的艺术:详解流式编程
46 1