Einführung
Es gibt viele Wissenschaften auf der Welt, die sich mit der Wahrscheinlichkeitstheorie befassen. Und die Wissenschaften bestehen aus verschiedenen Abschnitten. In der Mathematik gibt es beispielsweise einen eigenen Abschnitt, der sich mit der Untersuchung zufälliger Ereignisse, Mengen usw. befasst. Aber die Wissenschaft wird nicht auf die leichte Schulter genommen. In diesem Fall nahm die Wahrscheinlichkeitstheorie Gestalt an, als man versuchte zu verstehen, welche Muster es beim Würfeln bei Glücksspielen gab. Wenn man genau hinschaut, gibt es um uns herum viele scheinbar zufällige Dinge. Aber alles Zufällige ist nicht völlig zufällig. Aber dazu später mehr. Die Programmiersprache Java unterstützt seit der ersten Version des JDK auch Zufallszahlen. Zufallszahlen in Java können mit der Klasse
java.util.Random verwendet werden . Zum Testen verwenden wir
den Tutorialspoint Java Online Compiler . Hier ist ein einfaches Beispiel für die Verwendung von
Random , um das Werfen von „Würfeln“ oder Würfeln auf Russisch zu emulieren:
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);
}
}
Es scheint, dass dies das Ende der Beschreibung von
Random sein könnte , aber so einfach ist es nicht. Öffnen wir die Beschreibung der Klasse
java.util.Random in der Java-API. Und hier sehen wir interessante Dinge.
Die Random- Klasse verwendet Pseudozufallszahlen. Wie so? Es stellt sich heraus, dass Zufallszahlen nicht so zufällig sind?
Pseudozufälligkeit java.util.Random
Die Dokumentation für die Klasse
java.util.Random besagt, dass, wenn Instanzen
von Random mit demselben
Startparameter erstellt werden und dieselben Aktionssequenzen für die Instanzen ausgeführt werden, sie identische Zahlensequenzen zurückgeben. Und wenn wir genau hinschauen, können wir erkennen, dass
Random tatsächlich
einen Konstruktor hat, der einen Long- Wert als
Startwert akzeptiert :
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
Dieses Beispiel wird true zurückgeben, weil
Der Samen beider Instanzen ist derselbe. Was zu tun? Der Standardkonstruktor löst das Problem teilweise. Nachfolgend finden Sie ein Beispiel für den Inhalt des
Random- Konstruktors :
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
Der Standardkonstruktor verwendet die bitweise Exklusiv
-ODER- Operation . Und verwendet dafür
ein long , das die aktuelle Zeit darstellt, und etwas
Startwert :
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
Eine weitere interessante Sache hier ist, dass jeder Aufruf der getter-Methode
„seedUniquifier “ den Wert von „seedUniquifier“ ändert . Das heißt, die Klasse ist darauf ausgelegt, Zufallszahlen so effizient wie möglich auszuwählen. Allerdings
sind sie, wie es in der Dokumentation heißt, „nicht kryptografisch sicher “. Das heißt, für einige Verwendungszwecke für kryptografische Zwecke (Passwortgenerierung usw.) ist es nicht geeignet, weil Die Reihenfolge mit dem richtigen Ansatz wird vorhergesagt. Beispiele zu diesem Thema gibt es im Internet, zum Beispiel hier: „
Predicting the next Math.random() in Java “. Oder zum Beispiel den Quellcode hier: „
Vulnerability Weak Crypto “. java.util.Random (Zufallszahlengenerator) hat eine bestimmte „Abkürzung“,
also eine verkürzte Version des Aufrufs, der über Math.random ausgeführt wird:
public static void main(String []args){
int random_number = 1 + (int) (Math.random() * 6);
System.out.println("Value: " + random_number);
}
Aber wenn man genau hinschaut, sitzt darin derselbe Zufall:
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
Das JavaDoc empfiehlt die Verwendung
der SecureRandom- Klasse für einen „
kryptografisch sicheren Pseudozufallszahlengenerator “.
Sicheres zufälliges Java
Die SecureRandom- Klasse ist eine Unterklasse von
java.util.Random und befindet sich im Paket
java.security . Ein Vergleich dieser beiden Klassen kann im Artikel „
Unterschied zwischen java.util.Random und java.security.SecureRandom “ nachgelesen werden. Warum ist dieses SecureRandom so gut? Tatsache ist, dass für ihn die Quelle der Zufallszahlen ein so magisch klingendes Ding wie der „Kern-Entropie-Pool“ ist. Das ist sowohl ein Plus als auch ein Minus. Welche Nachteile dies mit sich bringt, können Sie im Artikel „
Die Gefahren von java.security.SecureRandom “ nachlesen. Kurz gesagt, Linux verfügt über einen Kernel-Zufallszahlengenerator (RNG). RNG generiert Zufallszahlen auf der Grundlage von Daten aus dem Entropiepool, der auf der Grundlage zufälliger Ereignisse im System wie Tastatur- und Festplatten-Timings, Mausbewegungen, Interrupts und Netzwerkverkehr gefüllt wird. Weitere Informationen zum Entropiepool finden Sie im Material „
Zufallszahlen in Linux (RNG) oder wie man /dev/random und /dev/urandom „füllt“ . Auf Windows-Systemen wird SHA1PRNG verwendet, implementiert in sun.security.provider.SecureRandom. Mit der Entwicklung von Java hat sich auch SecureRandom verändert, wofür es sich lohnt, im Testbericht „
Java SecureRandom-Updates ab April 2016 “ nachzulesen, um sich ein vollständiges Bild zu machen.
Multithreading oder sei wie Caesar
Wenn Sie sich den Code der
Random- Klasse ansehen , scheint nichts auf Probleme hinzuweisen. Methoden sind nicht als
synchronisiert markiert . Aber es gibt ein ABER: Wenn wir
Random mit dem Standardkonstruktor in mehreren Threads erstellen, teilen wir uns den gleichen
Instanz-Seed , wodurch
Random erstellt wird . Und wenn eine neue Zufallszahl empfangen wird, ändert sich auch die interne
AtomicLong der Instanz . Einerseits ist daran aus logischer Sicht nichts auszusetzen, denn...
AtomicLong wird verwendet . Andererseits muss man für alles bezahlen, auch für die Produktivität. Und auch dafür. Daher heißt es sogar in der offiziellen Dokumentation für
java.util.Random : „
Instanzen von java.util.Random sind threadsicher. Die gleichzeitige Verwendung derselben java.util.Random-Instanz über Threads hinweg kann jedoch zu Konflikten und daraus resultierender schlechter Leistung führen. Bedenken Sie.“ Verwenden Sie stattdessen ThreadLocalRandom in Multithread-Designs . Das heißt, in Multithread-Anwendungen ist es besser, die
ThreadLocalRandom- Klasse zu verwenden, wenn
Random aus mehreren Threads aktiv verwendet wird . Seine Verwendung unterscheidet sich geringfügig von der regulären
Random :
public static void main(String []args){
int rand = ThreadLocalRandom.current().nextInt(1,7);
System.out.println("Value: " + rand);
}
Wie Sie sehen, geben wir dafür keinen
Seed an . Dieses Beispiel wird im offiziellen Tutorial von Oracle:
Concurrent Random Numbers beschrieben . Weitere Informationen zu dieser Klasse finden Sie in der Rezension: „
Guide to ThreadLocalRandom in Java “.
StreamAPI und Random
Mit der Veröffentlichung von Java 8 haben wir viele neue Funktionen. Einschließlich Stream-API. Und die Änderungen wirkten sich auch auf die Generierung von
Zufallswerten aus .
Die Random- Klasse verfügt beispielsweise über neue Methoden, mit denen Sie
einen Stream mit Zufallswerten wie
int
,
double
oder abrufen können
long
. Zum Beispiel:
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));
}
}
Es gibt auch eine neue Klasse
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));
}
}
Mehr über den Unterschied zwischen
SplittableRandom und anderen Klassen können Sie hier lesen: „
Verschiedene Möglichkeiten, Zufallszahlen in Java zu erstellen “.
Abschluss
Ich denke, es lohnt sich, ein Fazit zu ziehen. Sie müssen das JavaDoc für die verwendeten Klassen sorgfältig lesen. Hinter etwas, das auf den ersten Blick so einfach ist wie Random, stecken Nuancen, die einen grausamen Witz spielen können. #Wjatscheslaw
GO TO FULL VERSION