JavaRush /Blog Java /Random-ES /Teoría de la probabilidad en la práctica o ¿conoces algo ...
Viacheslav
Nivel 3

Teoría de la probabilidad en la práctica o ¿conoces algo aleatorio?

Publicado en el grupo Random-ES
Teoría de probabilidad en la práctica o ¿conoces algo aleatorio? - 1

Introducción

Hay muchas ciencias en el mundo que estudian la teoría de la probabilidad. Y las ciencias constan de varias secciones. Por ejemplo, en matemáticas hay una sección separada dedicada al estudio de eventos aleatorios, cantidades, etc. Pero la ciencia no se toma a la ligera. En este caso, la teoría de la probabilidad comenzó a tomar forma cuando la gente intentó comprender qué patrones existían al lanzar dados en juegos de azar. Si miras de cerca, hay muchas cosas aparentemente aleatorias a nuestro alrededor. Pero todo lo aleatorio no es completamente aleatorio. Pero hablaremos de eso más adelante. El lenguaje de programación Java también admite números aleatorios, comenzando con la primera versión del JDK. Los números aleatorios en Java se pueden usar usando la clase java.util.Random . Para las pruebas, usaremos el compilador en línea Java de Tutorialspoint . A continuación se muestra un ejemplo primitivo del uso de Random para emular el lanzamiento de "dados" o cubos en ruso:
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);
    }
}
Parecería que este podría ser el final de la descripción de Random , pero no es tan sencillo. Abramos la descripción de la clase java.util.Random en la API de Java. Y aquí vemos cosas interesantes. La clase Random utiliza números pseudoaleatorios. ¿Cómo es eso? ¿Resulta que los números aleatorios no son tan aleatorios?
Teoría de la probabilidad en la práctica o ¿conoces algo aleatorio? - 2

Pseudoaleatoriedad java.util.Random

La documentación de la clase java.util.Random dice que si se crean instancias de Random con el mismo parámetro inicial y se realizan las mismas secuencias de acciones en las instancias, devuelven secuencias idénticas de números. Y si miramos de cerca, podemos ver que Random en realidad tiene un constructor que toma un valor largo como semilla:
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
Este ejemplo devolverá verdadero porque la semilla de ambas instancias es la misma. ¿Qué hacer? El constructor predeterminado resuelve parcialmente el problema. A continuación se muestra un ejemplo del contenido del constructor aleatorio :
public Random() {
	this(seedUniquifier() ^ System.nanoTime());
}
El constructor predeterminado utiliza la operación OR exclusiva bit a bit . Y para ello utiliza un largo que representa la hora actual y alguna semilla :
private static long seedUniquifier() {
	for (;;) {
		long current = seedUniquifier.get();
		long next = current * 181783497276652981L;
		if (seedUniquifier.compareAndSet(current, next))
			return next;
	}
}
Otra cosa interesante aquí es que cada llamada al método getter seedUniquifier cambia el valor de seedUniquifier . Es decir, la clase está diseñada para seleccionar números aleatorios de la manera más eficiente posible. Sin embargo, como dice la documentación, " no son criptográficamente seguros ". Es decir, para algunos fines de uso con fines criptográficos (generación de contraseñas, etc.) no es adecuado, porque Se predice la secuencia con el enfoque adecuado. Hay ejemplos sobre este tema en Internet, por ejemplo aquí: " Predecir el próximo Math.random() en Java ". O, por ejemplo, el código fuente aquí: " Vulnerabilidad criptográfica débil ". java.util.Random (generador de números aleatorios) tiene un determinado "atajo", es decir , una versión abreviada de la llamada que se ejecuta a través de Math.random:
public static void main(String []args){
	int random_number = 1 + (int) (Math.random() * 6);
	System.out.println("Value: " + random_number);
}
Pero si miras con atención, dentro se encuentra el mismo Random:
public static double random() {
	return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
	static final Random randomNumberGenerator = new Random();
}
El JavaDoc recomienda utilizar la clase SecureRandom para un " generador de números pseudoaleatorios criptográficamente seguro ".
Teoría de la probabilidad en la práctica o ¿conoces algo aleatorio? - 3

Java aleatorio seguro

La clase SecureRandom es una subclase de java.util.Random y se encuentra en el paquete java.security . Se puede leer una comparación de estas dos clases en el artículo " Diferencia entre java.util.Random y java.security.SecureRandom ". ¿Por qué es tan bueno este SecureRandom? El hecho es que, para él, la fuente de los números aleatorios es algo que suena tan mágico como el “grupo central de entropía”. Esto es a la vez un plus y un menos. Puede leer sobre las desventajas de esto en el artículo: " Los peligros de java.security.SecureRandom ". En resumen, Linux tiene un generador de números aleatorios (RNG) en el kernel. RNG genera números aleatorios basados ​​en datos del grupo de entropía, que se completa en función de eventos aleatorios en el sistema, como tiempos de teclado y disco, movimientos del mouse, interrupciones y tráfico de red. Se describe más información sobre el grupo de entropía en el material " Números aleatorios en Linux (RNG) o cómo “rellenar” /dev/random y /dev/urandom ". En los sistemas Windows, se utiliza SHA1PRNG, implementado en sun.security.provider.SecureRandom. Con el desarrollo de Java, SecureRandom también cambió, lo cual vale la pena leer en la revisión " Actualizaciones de Java SecureRandom a partir de abril de 2016 " para obtener una imagen completa.
Teoría de la probabilidad en la práctica o ¿conoces algo aleatorio? - 4

Multihilo o ser como César

Si observa el código de la clase Random , nada parece indicar problemas. Los métodos no están marcados como sincronizados . Pero hay un PERO: al crear Random con el constructor predeterminado en varios subprocesos, compartiremos la misma semilla de instancia entre ellos , mediante la cual se creará Random . Y también cuando se recibe un nuevo número aleatorio, el AtomicLong interno de la instancia también cambia . Por un lado, esto no tiene nada de malo desde un punto de vista lógico, porque... Se utiliza AtomicLong . Por otro lado, hay que pagar por todo, incluida la productividad. Y por esto también. Por lo tanto, incluso la documentación oficial de java.util.Random dice: " Las instancias de java.util.Random son seguras para subprocesos. Sin embargo, el uso simultáneo de la misma instancia de java.util.Random en varios subprocesos puede generar contención y, en consecuencia, un rendimiento deficiente. Considere en su lugar, utilice ThreadLocalRandom en diseños multiproceso ". Es decir, en aplicaciones multiproceso, cuando se utiliza activamente Random desde varios subprocesos, es mejor utilizar la clase ThreadLocalRandom . Su uso es ligeramente diferente al de Random normal :
public static void main(String []args){
	int rand = ThreadLocalRandom.current().nextInt(1,7);
	System.out.println("Value: " + rand);
}
Como puede ver, no especificamos una semilla para ello . Este ejemplo se describe en el tutorial oficial de Oracle: Números aleatorios concurrentes . Puede leer más sobre esta clase en la revisión: " Guía para ThreadLocalRandom en Java ".
Teoría de la probabilidad en la práctica o ¿conoces algo aleatorio? - 5

StreamAPI y aleatorio

Con el lanzamiento de Java 8, tenemos muchas características nuevas. Incluyendo Stream API. Y los cambios también afectaron la generación de valores aleatorios . Por ejemplo, la clase Random tiene nuevos métodos que le permiten obtener un Stream con valores aleatorios intcomo doubleo long. Por ejemplo:
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));
    }
}
También hay una nueva clase 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));
    }
}
Puede leer más sobre la diferencia entre SplittableRandom y otras clases aquí: " Diferentes formas de crear números aleatorios en Java ".

Conclusión

Creo que vale la pena sacar una conclusión. Debe leer atentamente el JavaDoc de las clases utilizadas. Detrás de algo tan sencillo a primera vista como Random hay matices que pueden jugar una broma cruel. #viacheslav
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION