JavaRush /Blogue Java /Random-PT /Teoria da probabilidade na prática ou você conhece Random...
Viacheslav
Nível 3

Teoria da probabilidade na prática ou você conhece Random

Publicado no grupo Random-PT
Teoria da probabilidade na prática ou você conhece Random - 1

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?
Teoria da probabilidade na prática ou você conhece Random - 2

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 ".
Teoria da probabilidade na prática ou você conhece Random - 3

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.
Teoria da probabilidade na prática ou você conhece Random - 4

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 ".
Teoria da probabilidade na prática ou você conhece Random - 5

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, doubleou 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
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION