La classe java.util.Random dans la bibliothèque de classes utilitaires Java fournit des méthodes pour générer différents types de nombres aléatoires. L'article suivant présente principalement des informations pertinentes sur le principe d'implémentation de java.util.Random. beaucoup de détails, les amis dans le besoin peuvent s'y référer.
Vue d'ensemble
java.util.Random peut générer des nombres aléatoires de types int, long, float, double et goussien. C'est également la plus grande différence entre cette méthode et la méthode Random() de java.lang.Math, qui génère uniquement des nombres aléatoires de type double.
Les instances de cette classe sont utilisées pour générer un flux de nombres pseudo-aléatoires. Cette classe utilise une graine de 48 bits modifiée par une formule congruente linéaire. Si deux instances de Random sont créées avec la même graine, elles généreront et renverront la même séquence de nombres en complétant la même séquence d'appels de méthode sur chaque instance.
Exemple
public class RandomTest { public static void main(String[] args) { testRandom(); System.out.println("---------------------"); testRandom(); System.out.println("---------------------"); testRandom(); } public static void testRandom(){ Random random = new Random(1); for(int i=0; i<5; i++){ System.out.print(random.nextInt()+"\t"); } System.out.println(""); } }
Résultat de sortie :
D'après les résultats, nous avons constaté que tant que les graines sont les mêmes, la séquence de nombres aléatoires obtenue est cohérente. Il s’agit d’une implémentation de nombres pseudo-aléatoires, et non de vrais nombres aléatoires.
Analyse aléatoire du code source
Structure de classe aléatoire
class Random implements java.io.Serializable { private final AtomicLong seed; private static final long multiplier = 0x5DEECE66DL; private static final long addend = 0xBL; private static final long mask = (1L << 48) - 1; private static final AtomicLong seedUniquifier = new AtomicLong(8682522807148012L);
Là sont des références La méthode de construction
public Random(long seed) { if (getClass() == Random.class) this.seed = new AtomicLong(initialScramble(seed)); else { // subclass might have overriden setSeed this.seed = new AtomicLong(); setSeed(seed); } } private static long initialScramble(long seed) { return (seed ^ multiplier) & mask; }
génère des nombres aléatoires en passant une graine À partir de l'exemple ci-dessus, on constate que la séquence de nombres aléatoires est générée par la même graine. est le même s'il est utilisé à chaque fois. Si vous souhaitez générer des séquences différentes, vous ne pouvez transmettre qu'une graine différente à chaque fois.
Méthode de construction sans paramètre
public Random() { this(seedUniquifier() ^ System.nanoTime()); } private static long seedUniquifier() { // L'Ecuyer, "Tables of Linear Congruential Generators of // Different Sizes and Good Lattice Structure", 1999 for (;;) { long current = seedUniquifier.get(); long next = current * 181783497276652981L; if (seedUniquifier.compareAndSet(current, next)) return next; } }
Découvert grâce au code source, le paramètre- moins de méthode de construction, il génère automatiquement une graine pour nous et utilise la méthode de spin CAS pour s'assurer que la graine obtenue est différente à chaque fois, garantissant ainsi que la séquence aléatoire obtenue à chaque fois new Random()
est incohérente.
Méthode nextInt() : Obtenez un nombre aléatoire int
public int nextInt() { return next(32); } protected int next(int bits) { long oldseed, nextseed; AtomicLong seed = this.seed; do { oldseed = seed.get(); nextseed = (oldseed * multiplier + addend) & mask; } while (!seed.compareAndSet(oldseed, nextseed)); return (int)(nextseed >>> (48 - bits)); }
À partir du code que nous pouvons On constate que tant que la graine est déterminée, le nombre généré à chaque fois est généré à l'aide d'un algorithme fixe, donc tant que la graine est déterminée, la séquence générée à chaque fois est fixe.
Chaque fois que la graine est mise à jour, CAS est utilisé pour la mettre à jour. Dans un environnement à haute concurrence, les performances sont un problème.
Problèmes de sécurité
Imaginez s'il s'agit d'une plateforme de loterie, tant que la graine est déterminée, la séquence générée sera la même à chaque temps . De cette manière, cette faille peut être utilisée pour prédire les numéros du prochain tirage de loterie, ce qui peut facilement être exploité par certaines personnes.
jdk vous recommande d'essayer d'utiliser SecureRandom pour générer des nombres aléatoires.
SecureRandom
SecureRandom est un puissant générateur de nombres aléatoires. Les principaux scénarios d'application sont : des numéros de données à des fins de sécurité, tels que la génération de secrets. clé ou identifiant de session. Dans l'article ci-dessus "Sécurité des nombres pseudo-aléatoires", les problèmes de sécurité des générateurs de nombres aléatoires faibles vous ont été révélés, et l'utilisation de générateurs de nombres aléatoires puissants comme SecureRandom réduira considérablement le risque de choses. ça va mal.
Pour générer des nombres aléatoires à haute résistance, il existe deux facteurs importants : la graine et l'algorithme. Il peut y avoir de nombreux algorithmes et le choix de la graine est généralement un facteur très critique. Par exemple, Random, sa graine est System.currentTimeMillis(), donc ses nombres aléatoires sont des nombres pseudo-aléatoires prévisibles et faibles.
L'idée de générer des nombres pseudo-aléatoires forts : collecter diverses informations sur l'ordinateur, le temps de saisie au clavier, l'état d'utilisation de la mémoire, l'espace libre du disque dur, le délai d'E/S, le nombre de processus, le nombre de threads et d'autres informations, l'horloge du processeur, pour obtenir un nombre approximativement aléatoire, les graines atteignent principalement l'imprévisibilité.
Pour faire simple, utilisez un algorithme de cryptage pour générer une graine aléatoire très longue, afin que vous ne puissiez pas deviner la graine, et donc ne pas déduire le numéro de séquence aléatoire.
Problèmes de performances aléatoires
À partir du code source aléatoire, nous avons découvert que CAS est utilisé pour mettre à jour la graine chaque fois qu'un nombre aléatoire est obtenu. valeur. De cette manière, il y aura un grand nombre de tentatives CAS dans un environnement à forte concurrence, ce qui entraînera une dégradation des performances. À l’heure actuelle, il est recommandé d’utiliser la classe ThreadLocalRandom pour générer des nombres aléatoires.
Principe d'implémentation de ThreadLocalRandom
Classe Thread
Il existe une propriété threadLocalRandomSeed dans la classe Thread.
Structure ThreadLocalRandom
La variable SEED est le décalage de threadLocalRandomSeed dans l'objet Thread.
Méthode ThreadLocalRandom.nextSeed()
À partir de cette méthode, nous constatons que la valeur de départ de chaque thread est stockée dans In la propriété threadLocalRandomSeed de l'objet Thread.
Conclusion
Étant donné que les graines de ThreadLocalRandom sont stockées dans des objets Thread, CAS ne sera pas utilisé pour garantir une concurrence élevée lors de l'acquisition d'objets Random. Les valeurs obtenues à chaque fois sont incohérentes.
Chaque thread conserve sa propre graine. Lorsque chaque thread doit obtenir des nombres aléatoires, il obtient la graine du thread actuel à partir de l'objet Thread actuel et obtient des nombres aléatoires. Les performances sont grandement améliorées.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!