Introdução
Existem muitas ciências no mundo que estudam a teoria da probabilidade. E as ciências consistem em várias seções. Por exemplo, em matemática há uma seção separada dedicada ao estudo de eventos aleatórios, quantidades, etc. Mas a ciência não é encarada levianamente. Nesse caso, a teoria da probabilidade começou a tomar forma quando as pessoas tentaram entender quais padrões existiam no lançamento de dados em jogos de azar. Se você olhar de perto, há muitas coisas aparentemente aleatórias ao nosso redor. Mas tudo que é aleatório não é completamente aleatório. Mas falaremos mais sobre isso mais tarde. A linguagem de programação Java também possui suporte para números aleatórios, começando com a primeira versão do JDK. Números aleatórios em Java podem ser usados usando a classe
java.util.Random . Para testes, usaremos
o compilador online tutorialspoint java . Aqui está um exemplo primitivo de uso de
Random para emular o lançamento de “dados” ou cubos em russo:
import java.util.Random;
public class HelloWorld{
public static void main(String []args){
Random rnd = new Random();
int number = rnd.nextInt(6) + 1;
System.out.println("Random number: " + number);
}
}
Parece que este poderia ser o fim da descrição de
Random , mas não é tão simples assim. Vamos abrir a descrição da classe
java.util.Random na API Java. E aqui vemos coisas interessantes. A classe
Random usa números pseudo-aleatórios. Como assim? Acontece que os números aleatórios não são tão aleatórios?
Pseudo-aleatoriedade java.util.Random
A documentação da classe
java.util.Random diz que se instâncias
de Random forem criadas com o mesmo parâmetro
inicial e as mesmas sequências de ações forem executadas nas instâncias, elas retornarão sequências idênticas de números. E se olharmos de perto, podemos ver que
Random na verdade tem
um construtor que recebe algum valor
longo como semente:
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
Este exemplo retornará
verdadeiro porque
a semente de ambas as instâncias é a mesma. O que fazer? O construtor padrão resolve parcialmente o problema. Abaixo está um exemplo do conteúdo do construtor
Random :
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
O construtor padrão usa a operação
OR exclusiva bit a bit .
E usa um long representando a hora atual e alguma
semente para isso :
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
Outra coisa interessante aqui é que cada chamada ao método getter
seedUniquifier altera o valor
de seedUniquifier . Ou seja, a classe foi projetada para selecionar números aleatórios da maneira mais eficiente possível. No entanto, como diz a documentação, eles “
não são criptograficamente seguros ”. Ou seja, para alguns fins de uso criptográfico (geração de senha, etc.) não é adequado, pois a sequência com a abordagem adequada é prevista. Existem exemplos sobre este assunto na Internet, por exemplo aqui: “
Prevendo o próximo Math.random() em Java ”. Ou por exemplo o código fonte aqui: "
Vulnerability Weak Crypto ". java.util.Random (gerador de números aleatórios) possui um certo “atalho”, ou seja
, uma versão abreviada da chamada que é executada através de Math.random:
public static void main(String []args){
int random_number = 1 + (int) (Math.random() * 6);
System.out.println("Value: " + random_number);
}
Mas se você olhar com atenção, o mesmo Random está lá dentro:
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
O JavaDoc recomenda o uso
da classe SecureRandom para um "
gerador de números pseudo-aleatórios criptograficamente seguro ".
Java aleatório seguro
A classe
SecureRandom é uma subclasse de
java.util.Random e está localizada no pacote
java.security . Uma comparação entre essas duas classes pode ser lida no artigo "
Diferença entre java.util.Random e java.security.SecureRandom ". Por que este SecureRandom é tão bom? O fato é que, para ele, a fonte dos números aleatórios é algo que parece tão mágico quanto o “conjunto central de entropia”. Isso é um ponto positivo e um ponto negativo. Você pode ler sobre as desvantagens disso no artigo: “
Os perigos de java.security.SecureRandom ”. Resumindo, o Linux possui um gerador de números aleatórios (RNG) no kernel. O RNG gera números aleatórios com base nos dados do pool de entropia, que é preenchido com base em eventos aleatórios no sistema, como temporizações de teclado e disco, movimentos do mouse, interrupções e tráfego de rede. Mais informações sobre o pool de entropia estão descritas no material "
Números aleatórios no Linux (RNG) ou como “preencher” /dev/random e /dev/urandom ". Em sistemas Windows, é usado SHA1PRNG, implementado em sun.security.provider.SecureRandom. Com o desenvolvimento do Java, o SecureRandom também mudou, o que vale a pena ler na revisão “
Atualizações do Java SecureRandom em abril de 2016 ” para uma visão completa.
Multithreading ou seja como César
Se você observar o código da classe
Random , nada parece indicar problema. Os métodos não são marcados
como sincronizados . Mas há um MAS: ao criar
Random com o construtor padrão em vários threads, compartilharemos a mesma
semente de instância entre eles , pela qual
Random será criado . E também quando um novo número aleatório é recebido,
o AtomicLong interno da instância também muda . Por um lado, não há nada de errado com isto do ponto de vista lógico, porque...
AtomicLong é usado . Por outro lado, você tem que pagar por tudo, inclusive pela produtividade. E por isso também. Portanto, até mesmo a documentação oficial de
java.util.Random diz: "
Instâncias de java.util.Random são threadsafe. No entanto, o uso simultâneo da mesma instância java.util.Random entre threads pode encontrar contenção e consequente baixo desempenho. Considere em vez disso, use ThreadLocalRandom em designs multithread ". Ou seja, em aplicativos multithread, ao usar ativamente
Random de vários threads, é melhor usar a classe
ThreadLocalRandom . Seu uso é um pouco diferente do
Random normal :
public static void main(String []args){
int rand = ThreadLocalRandom.current().nextInt(1,7);
System.out.println("Value: " + rand);
}
Como você pode ver, não especificamos
uma semente para isso . Este exemplo é descrito no tutorial oficial da Oracle:
Concurrent Random Numbers . Você pode ler mais sobre esta classe na revisão: "
Guia para ThreadLocalRandom em Java ".
StreamAPI e aleatório
Com o lançamento do Java 8, temos muitos recursos novos. Incluindo API de fluxo. E as mudanças também afetaram a geração de valores
aleatórios . Por exemplo, a classe
Random possui novos métodos que permitem obter
um Stream com valores aleatórios como
int
,
double
ou
long
. Por exemplo:
import java.util.Random;
public class HelloWorld{
public static void main(String []args){
new Random().ints(10, 1, 7).forEach(n -> System.out.println(n));
}
}
Há também uma nova classe
SplittableRandom :
import java.util.SplittableRandom;
public class HelloWorld{
public static void main(String []args){
new SplittableRandom().ints(10, 1, 7).forEach(n -> System.out.println(n));
}
}
Você pode ler mais sobre a diferença entre
SplittableRandom e outras classes aqui: "
Diferentes maneiras de criar números aleatórios em Java ".
Conclusão
Acho que vale a pena tirar uma conclusão. Você precisa ler atentamente o JavaDoc para as classes utilizadas. Por trás de algo tão simples à primeira vista como Random, existem nuances que podem ser uma piada cruel. #Viacheslav
GO TO FULL VERSION