Introduction
Il existe de nombreuses sciences dans le monde qui étudient la théorie des probabilités. Et les sciences se composent de différentes sections. Par exemple, en mathématiques, il existe une section distincte consacrée à l'étude des événements aléatoires, des quantités, etc. Mais la science n’est pas prise à la légère. Dans ce cas, la théorie des probabilités a commencé à prendre forme lorsque les gens ont essayé de comprendre quels schémas existaient les lancers de dés lors des jeux de hasard. Si vous regardez attentivement, il y a beaucoup de choses apparemment aléatoires autour de nous. Mais tout ce qui est aléatoire n’est pas complètement aléatoire. Mais plus là-dessus plus tard. Le langage de programmation Java prend également en charge les nombres aléatoires, à partir de la première version du JDK. Les nombres aléatoires en Java peuvent être utilisés à l'aide de la classe
java.util.Random . Pour les tests, nous utiliserons
le compilateur en ligne java tutorielspoint . Voici un exemple primitif d’utilisation
de Random pour émuler le lancement de « dés » ou de cubes en russe :
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);
}
}
Il semblerait que cela puisse être la fin de la description de
Random , mais ce n'est pas si simple. Ouvrons la description de la classe
java.util.Random dans l'API Java. Et là, nous voyons des choses intéressantes. La classe
Random utilise des nombres pseudo-aléatoires. Comment ça? Il s'avère que les nombres aléatoires ne sont pas si aléatoires ?
Pseudo-aléatoire java.util.Random
La documentation de la classe
java.util.Random indique que si des instances
de Random sont créées avec le même paramètre
de départ et que les mêmes séquences d'actions sont effectuées sur les instances, elles renvoient des séquences de nombres identiques. Et si nous regardons attentivement, nous pouvons voir que
Random a en fait
un constructeur qui prend une valeur
longue comme graine :
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
Cet exemple retournera
vrai car
la graine des deux instances est la même. Ce qu'il faut faire? Le constructeur par défaut résout en partie le problème. Ci-dessous un exemple du contenu du constructeur
Random :
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
Le constructeur par défaut utilise l' opération
OR exclusive au niveau du bit .
Et utilise un long représentant l'heure actuelle et quelques
graines pour cela :
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
Une autre chose intéressante ici est que chaque appel à la méthode getter
seedUniquifier modifie la valeur
de seedUniquifier . Autrement dit, la classe est conçue pour sélectionner des nombres aléatoires aussi efficacement que possible. Cependant, comme le dit la documentation, ils «
ne sont pas sécurisés cryptographiquement ». Autrement dit, à certaines fins d'utilisation à des fins cryptographiques (génération de mot de passe, etc.), il ne convient pas, car la séquence avec la bonne approche est prédite. Il existe des exemples sur ce sujet sur Internet, par exemple ici : «
Prédire le prochain Math.random() en Java ». Ou par exemple le code source ici : «
Vulnérabilité Weak Crypto ». java.util.Random (générateur de nombres aléatoires) a un certain « raccourci », c'est-à
- dire une version abrégée de l'appel exécuté via Math.random :
public static void main(String []args){
int random_number = 1 + (int) (Math.random() * 6);
System.out.println("Value: " + random_number);
}
Mais si vous regardez attentivement, le même Random se trouve à l’intérieur :
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
Le JavaDoc conseille d'utiliser
la classe SecureRandom pour un "
générateur de nombres pseudo-aléatoires cryptographiquement sécurisé ".
Java aléatoire sécurisé
La classe
SecureRandom est une sous-classe de
java.util.Random et se trouve dans le package
java.security . Une comparaison de ces deux classes peut être lue dans l'article «
Différence entre java.util.Random et java.security.SecureRandom ». Pourquoi ce SecureRandom est-il si bon ? Le fait est que pour lui, la source des nombres aléatoires est une chose aussi magique que le « pool d’entropie central ». C'est à la fois un plus et un moins. Vous pouvez en savoir plus sur les inconvénients de cela dans l'article : «
Les dangers de java.security.SecureRandom ». En bref, Linux dispose d'un générateur de nombres aléatoires (RNG) dans le noyau. RNG génère des nombres aléatoires basés sur les données du pool d'entropie, qui est rempli en fonction d'événements aléatoires dans le système, tels que les timings du clavier et du disque, les mouvements de la souris, les interruptions et le trafic réseau. Plus d'informations sur le pool d'entropie sont décrites dans le matériel "
Les nombres aléatoires sous Linux (RNG) ou comment « remplir » /dev/random et /dev/urandom ". Sur les systèmes Windows, SHA1PRNG est utilisé, implémenté dans sun.security.provider.SecureRandom. Avec le développement de Java, SecureRandom a également changé, ce qui mérite d'être lu dans la revue «
Mises à jour Java SecureRandom d'avril 2016 » pour une image complète.
Multithreading ou être comme César
Si vous regardez le code de la classe
Random , rien ne semble indiquer un problème. Les méthodes ne sont pas marquées
synchronisées . Mais il y en a un MAIS : lors de la création
de Random avec le constructeur par défaut dans plusieurs threads, nous partagerons la même
graine d'instance entre eux , par laquelle
Random sera créé . Et également lorsqu'un nouveau nombre aléatoire est reçu,
l'AtomicLong interne de l'instance change également . D'un côté, il n'y a rien de mal à cela d'un point de vue logique, car...
AtomicLong est utilisé . En revanche, il faut tout payer, y compris la productivité. Et pour cela aussi. Par conséquent, même la documentation officielle de
java.util.Random dit : «
Les instances de java.util.Random sont threadsafe. Cependant, l'utilisation simultanée de la même instance java.util.Random sur plusieurs threads peut rencontrer des conflits et, par conséquent, de mauvaises performances. Considérez à la place, utilisez ThreadLocalRandom dans les conceptions multithread ". Autrement dit, dans les applications multithreads, lors de l'utilisation active de
Random à partir de plusieurs threads, il est préférable d'utiliser la classe
ThreadLocalRandom . Son utilisation est légèrement différente de
Random :
public static void main(String []args){
int rand = ThreadLocalRandom.current().nextInt(1,7);
System.out.println("Value: " + rand);
}
Comme vous pouvez le voir, nous ne spécifions pas
de graine pour cela . Cet exemple est décrit dans le tutoriel officiel d'Oracle :
Concurrent Random Numbers . Vous pouvez en savoir plus sur cette classe dans la revue : "
Guide de ThreadLocalRandom en Java ".
StreamAPI et aléatoire
Avec la sortie de Java 8, nous disposons de nombreuses nouvelles fonctionnalités. Y compris l'API Stream. Et les changements ont également affecté la génération de valeurs
aléatoires . Par exemple, la classe
Random dispose de nouvelles méthodes qui vous permettent d'obtenir
un Stream avec des valeurs aléatoires comme
int
,
double
ou
long
. Par exemple:
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));
}
}
Il existe également une nouvelle 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));
}
}
Vous pouvez en savoir plus sur la différence entre
SplittableRandom et les autres classes ici : "
Différentes façons de créer des nombres aléatoires en Java ".
Conclusion
Je pense que cela vaut la peine de tirer une conclusion. Vous devez lire attentivement le JavaDoc pour les classes utilisées. Derrière quelque chose d'aussi simple à première vue que Random se cachent des nuances qui peuvent constituer une blague cruelle. #Viacheslav
GO TO FULL VERSION