4 способа генерации случайных чисел в Java!

Java задняя часть
4 способа генерации случайных чисел в Java!

В Java существует множество сценариев генерации случайных чисел, поэтому в этой статье мы рассмотрим 4 способа генерации случайных чисел, различия между ними и сценарии, соответствующие каждому методу генерации.

1.Random

Класс Random родился в JDK 1.0, и генерируемые им случайные числа являются псевдослучайными числами, то есть обычными случайными числами. Случайный алгоритм, используемый Random, представляет собой генератор линейных конгруэнтных псевдослучайных чисел (LGC). При генерации случайных чисел исходное число случайного алгоритма называется начальным числом, и на основе начального числа выполняется определенное преобразование для генерации требуемого случайного числа.

Случайные объекты генерируют одни и те же случайные числа одинаковое количество раз, когда количество семян одинаково.. Например, для двух объектов Random с одинаковым начальным числом случайные числа, сгенерированные в первый раз, будут точно такими же, и случайные числа, сгенерированные во второй раз, также будут одинаковыми.По умолчанию new Random() использует текущее время в наносекундах в качестве начального числа..

① Основное использование

Используйте Random для генерации случайного числа от 0 до 10 (исключая 10), код реализации выглядит следующим образом:

// 生成 Random 对象
Random random = new Random();
for (int i = 0; i < 10; i++) {
    // 生成 0-9 随机整数
    int number = random.nextInt(10);
    System.out.println("生成随机数:" + number);
}

Результат выполнения вышеуказанной программы:image.png

② Анализ преимуществ и недостатков

Random генерирует псевдослучайное число с использованием алгоритма LGC.Преимуществом является реализация более эффективных, быстро генерируемых. ​

этоНедостатком является то, что если случайное начальное число Random одинаковое, случайное число, генерируемое каждый раз, предсказуемо (все равно). Как показано в следующем коде, когда мы устанавливаем одно и то же начальное число для двух потоков, мы обнаружим, что случайные числа, генерируемые каждый раз, одинаковы:

 // 创建两个线程
for (int i = 0; i < 2; i++) {
    new Thread(() -> {
        // 创建 Random 对象,设置相同的种子
        Random random = new Random(1024);
        // 生成 3 次随机数
        for (int j = 0; j < 3; j++) {
            // 生成随机数
            int number = random.nextInt();
            // 打印生成的随机数
            System.out.println(Thread.currentThread().getName() + ":" +
                               number);
            // 休眠 200 ms
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("---------------------");
        }
    }).start();
}

Результат выполнения вышеуказанной программы:

image.png

③ Вопросы безопасности потоков

Когда мы собираемся использовать класс, первый вопрос, который нас волнует, это: является ли он потокобезопасным? Для случайного,Random является потокобезопасным. ​

PS: Потокобезопасность означает, что в многопоточном сценарии результат выполнения программы согласуется с ожидаемым результатом, который называется потокобезопасностью, в противном случае он не является потокобезопасным (также называется потокобезопасностью). Например, если есть два потока, первый поток выполняет 100 000 ++ операций, а второй поток выполняет 100 000 -- операций, то конечный результат не нужно ни складывать, ни вычитать.Если конечный результат программы не соответствует ожиданиям, это не потокобезопасно.

Давайте посмотрим на исходный код реализации Random:

public Random() {
    this(seedUniquifier() ^ System.nanoTime());
}

public int nextInt() {
    return next(32);
}

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)); // CAS(Compare and Swap)生成随机数
    return (int)(nextseed >>> (48 - bits));
}

PS: весь исходный код в этой статье взят из JDK 1.8.0_211.

Как видно из приведенного выше исходного кода, нижний уровень Random использует CAS (Сравнить и поменять местами, сравнить и заменить) для решения проблемы безопасности потоков, поэтому для подавляющего большинства сценариев генерации случайных чисел использование Random является хорошим выбор. . ​

PS: есть два типа атомарных операций, реализованных механизмом параллелизма Java: один — это блокировка, а другой — CAS. ​

CAS — это сокращение от Compare And Swap (сравнить и заменить).Многие классы в java.util.concurrent.atomic, такие как (AtomicInteger AtomicBoolean AtomicLong и т. д.), реализованы с использованием механизма CAS.

2.ThreadLocalRandom

ThreadLocalRandom — это новый класс, предоставляемый JDK 1.7. Он является членом JUC (java.util.concurrent).Почему после Random создается еще один ThreadLocalRandom? ​

Причина очень проста. Из приведенного выше исходного кода Random видно, что Random использует CAS для решения проблемы безопасности потоков при генерации случайных чисел. Однако CAS очень неэффективен в сценариях с интенсивной конкуренцией потоков. Причина в том, что другие потоки всегда изменяют исходное значение во время сравнения CAS, поэтому сравнение CAS терпит неудачу, поэтому ему приходится продолжать цикл, чтобы попробовать операцию CAS. такВ сценариях с интенсивной многопоточной конкуренцией ThreadLocalRandom можно использовать для решения проблемы низкой эффективности выполнения Random.. ​

Когда мы впервые видим ThreadLocalRandom, мы должны один раз подумать о классе ThreadLocal, и это правда.Принцип реализации ThreadLocalRandom подобен принципу ThreadLocal и эквивалентен предоставлению каждому потоку собственного локального начального числа, чтобы избежать дополнительных издержек производительности, вызванных конкуренцией нескольких потоков за начальное число..

① Основное использование

Далее мы используем ThreadLocalRandom для генерации случайного числа от 0 до 10 (исключая 10), код реализации выглядит следующим образом:

// 得到 ThreadLocalRandom 对象
ThreadLocalRandom random = ThreadLocalRandom.current();
for (int i = 0; i < 10; i++) {
    // 生成 0-9 随机整数
    int number = random.nextInt(10);
    // 打印结果
    System.out.println("生成随机数:" + number);
}

Результат выполнения вышеуказанной программы:image.png

② Принцип реализации

Принцип реализации ThreadLocalRandom аналогичен принципу ThreadLocal. Он позволяет каждому потоку хранить собственное локальное начальное число, которое будет инициализировано только при генерации случайных чисел. Исходный код реализации выглядит следующим образом:

public int nextInt(int bound) {
    // 参数效验
    if (bound <= 0)
        throw new IllegalArgumentException(BadBound);
    // 根据当前线程中种子计算新种子
    int r = mix32(nextSeed());
    int m = bound - 1;
    // 根据新种子和 bound 计算随机数
    if ((bound & m) == 0) // power of two
        r &= m;
    else { // reject over-represented candidates
        for (int u = r >>> 1;
             u + m - (r = u % bound) < 0;
             u = mix32(nextSeed()) >>> 1)
            ;
    }
    return r;
}

final long nextSeed() {
    Thread t; long r; // read and update per-thread seed
    // 获取当前线程中 threadLocalRandomSeed 变量,然后在种子的基础上累加 GAMMA 值作为新种子
    // 再使用 UNSAFE.putLong 将新种子存放到当前线程的 threadLocalRandomSeed 变量中
    UNSAFE.putLong(t = Thread.currentThread(), SEED,
                   r = UNSAFE.getLong(t, SEED) + GAMMA); 
    return r;
}

③ Анализ преимуществ и недостатков

И комбинация класса Random ThreadLocalRandom ThreadLocal и изолирована в текущем потоке. Таким образом, он сеет, избегая операций конкуренции, таким образомЛучшая производительность в многопотативных средах, а также гарантирует егопотокобезопасность.

Кроме того, в отличие от Random, ThreadLocalRandom явно не поддерживает установку случайных начальных значений. Он переопределяет RandomsetSeed(long seed)метод и напрямую бросаетUnsupportedOperationExceptionИсключение, такВозможность уменьшения случайного количества случайных чисел в нескольких потоках.

Исходный код выглядит следующим образом:

public void setSeed(long seed) {
    // only allow call from super() constructor
    if (initialized)
        throw new UnsupportedOperationException();
}

Выдает, пока в программе вызывается метод setSeed()UnsupportedOperationExceptionисключение, как показано на следующем рисунке:image.png

Анализ недостатков ThreadLocalRandom

Хотя ThreadLocalRandom не поддерживает метод ручной установки случайных начальных значений, это не означает, что ThreadLocalRandom идеален.Когда мы смотрим на исходный код initialSeed(), метода инициализации случайных начальных значений в ThreadLocalRandom, мы обнаруживаем, что по умолчанию его случайные семена также связаны с текущим временем следующим образом:

private static long initialSeed() {
    // 尝试获取 JVM 的启动参数
    String sec = VM.getSavedProperty("java.util.secureRandomSeed");
    // 如果启动参数设置的值为 true,则参数一个随机 8 位的种子
    if (Boolean.parseBoolean(sec)) {
        byte[] seedBytes = java.security.SecureRandom.getSeed(8);
        long s = (long)(seedBytes[0]) & 0xffL;
        for (int i = 1; i < 8; ++i)
            s = (s << 8) | ((long)(seedBytes[i]) & 0xffL);
        return s;
    }
    // 如果没有设置启动参数,则使用当前时间有关的随机种子算法
    return (mix64(System.currentTimeMillis()) ^
            mix64(System.nanoTime()));
}

Как видно из вышеуказанного исходного кода, когда мы устанавливаем параметр запуска «-Djava.util.secureNAndomseed = true», ThreadLoCalrandom будет генерировать случайное семя, что может облегчить проблему предсказуемых случайных чисел, вызванных тем же случайным сеемм для определенная степень. ОднакоПо умолчанию, если этот параметр не установлен, несколько потоков могут генерировать одно и то же случайное число на каждом этапе из-за одинакового времени запуска в нескольких потоках..

3.SecureRandom

SecureRandom наследуется от Random, который обеспечивает криптографически надежный генератор случайных чисел.В отличие от Random, SecureRandom собирает некоторые случайные события, такие как щелчки мышью, нажатия клавиш и т. д. SecureRandom использует эти случайные события в качестве начальных значений. Это означает, что семена непредсказуемы., в отличие от Random, который по умолчанию использует миллисекунды текущего системного времени в качестве начального числа, что позволяет избежать возможности генерации одного и того же случайного числа. ​

Основное использование

// 创建 SecureRandom 对象,并设置加密算法
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
for (int i = 0; i < 10; i++) {
    // 生成 0-9 随机整数
    int number = random.nextInt(10);
    // 打印结果
    System.out.println("生成随机数:" + number);
}

Результат выполнения вышеуказанной программы:image.pngSecureRandom по умолчанию поддерживает два алгоритма шифрования:

  1. алгоритм SHA1PRNG, предоставленный sun.security.provider.SecureRandom;
  2. Алгоритм NativePRNG, предоставленный sun.security.provider.NativePRNG.

Конечно, в дополнение к вышеуказанным методам работы вы также можете использоватьnew SecureRandom()Для создания объекта SecureRandom код реализации выглядит следующим образом:

SecureRandom secureRandom = new SecureRandom();

Инициализация SecuberAndom с новыми, алгоритм NaturePrng используется для генерации случайных чисел по умолчанию, но вы также можете настроить параметр запуска JVM «-Djava.security» для изменения алгоритма для генерации случайных чисел или выбрать для использования алгоритма.getInstance("算法名称")Способ указать алгоритм генерации случайных чисел.

4.Math

Класс Math родился в JDK 1.0.Он содержит свойства и методы для выполнения основных математических операций, таких как элементарные экспоненты, логарифмы, квадратные корни и тригонометрические функции.Конечно, он также содержит статические методы для генерации случайных чисел.Math.random(),Этот метод производит двойное значение от 0 до 1, как показано в коде ниже.

① Основное использование

for (int i = 0; i < 10; i++) {
    // 产生随机数
    double number = Math.random();
    System.out.println("生成随机数:" + number);
}

Результат выполнения вышеуказанной программы:image.png

② расширение

Конечно, если ты хочешьиспользуйте его для генерации диапазона значений intТакже возможно, вы можете написать:

for (int i = 0; i < 10; i++) {
    // 生成一个从 0-99 的整数
    int number = (int) (Math.random() * 100);
    System.out.println("生成随机数:" + number);
}

Результат выполнения вышеуказанной программы:image.png

③ Принцип реализации

после анализаMathИсходный код мы можем узнать: Когда первый звонокMath.random()Способ, генератор псевдослучайных чисел автоматически создан автоматически, ** На самом деле используется **new java.util.Random(), в следующий раз, когда вы продолжите звонитьMath.random()используется этот новый генератор псевдослучайных чисел. ​

Исходный код выглядит следующим образом:

public static double random() {
    return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}

private static final class RandomNumberGeneratorHolder {
    static final Random randomNumberGenerator = new Random();
}

Суммировать

В этой статье мы представляем 4 метода генерации случайных чисел, среди которых Math — пакет для Random, поэтому они похожи. Random генерирует псевдослучайные числа с текущим наносекундным временем в качестве начального числа, и в случае интенсивной многопоточной конкуренции возникают определенные проблемы с производительностью из-за необходимости выполнения операций CAS, ноДля большинства сценариев приложений используйте случайные. ThreadLocalrandom можно использовать для замены случайных, когда конкурентные сцены высоки, но если требования к безопасности являются относительно высокими, вы можете использовать Secualrandom для генерации случайных чисел., потому что SecureRandom будет собирать некоторые случайные события как случайные начальные числа, поэтому SecureRandom можно рассматривать как класс инструментов для генерации действительно случайных чисел.

Ссылки и благодарности

woo woo .cn blog on.com/for andrology 1215/afraid…

blog.csdn.net/lycyingO/article/details/95276195

Подпишитесь на официальный аккаунт «Сообщество китайского языка Java», чтобы увидеть больше интересных и полезных статей о параллельном программировании.