JavaRush /Blog Java /Random-FR /Théorie des probabilités en pratique ou connaissez-vous l...
Viacheslav
Niveau 3

Théorie des probabilités en pratique ou connaissez-vous le hasard

Publié dans le groupe Random-FR
Théorie des probabilités en pratique ou connaissez-vous l'Aléatoire - 1

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 ?
Théorie des probabilités en pratique ou connaissez-vous Random - 2

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é ".
Théorie des probabilités en pratique ou connaissez-vous l'Aléatoire - 3

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.
Théorie des probabilités en pratique ou connaissez-vous l'Aléatoire - 4

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 ".
Théorie des probabilités en pratique ou connaissez-vous l'Aléatoire - 5

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