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?
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 ".
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.
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 ".
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
int
como
double
o
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
GO TO FULL VERSION