JavaRush /Blog Java /Random-PL /Teoria prawdopodobieństwa w praktyce, czyli znasz Random
Viacheslav
Poziom 3

Teoria prawdopodobieństwa w praktyce, czyli znasz Random

Opublikowano w grupie Random-PL
Teoria prawdopodobieństwa w praktyce czyli czy znasz Random - 1

Wstęp

Na świecie istnieje wiele nauk zajmujących się teorią prawdopodobieństwa. Nauki składają się z różnych działów. Na przykład w matematyce istnieje osobna sekcja poświęcona badaniu zdarzeń losowych, ilości itp. Ale nauki nie można lekceważyć. W tym przypadku teoria prawdopodobieństwa zaczęła nabierać kształtu, gdy ludzie próbowali zrozumieć, jakie wzorce obowiązują podczas rzucania kostką podczas grania w gry losowe. Jeśli przyjrzysz się uważnie, wokół nas znajduje się wiele pozornie przypadkowych rzeczy. Ale nie wszystko, co losowe, jest całkowicie losowe. Ale o tym później. Język programowania Java obsługuje również liczby losowe, począwszy od pierwszej wersji JDK. Liczb losowych w Javie można używać przy użyciu klasy java.util.Random . Do testów użyjemy kompilatora online Java tutorialspoint . Oto prymitywny przykład użycia Random do emulacji rzucania „kostkami” lub kostkami w języku rosyjskim:
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);
    }
}
Wydawać by się mogło, że na tym można zakończyć opis Random , jednak nie jest to takie proste. Otwórzmy opis klasy java.util.Random w Java API. I tutaj widzimy ciekawe rzeczy. Klasa Random wykorzystuje liczby pseudolosowe. Jak to? Okazuje się, że liczby losowe nie są aż tak losowe?
Teoria prawdopodobieństwa w praktyce czyli czy znasz Random - 2

Pseudolosowość java.util.Random

Dokumentacja klasy java.util.Random mówi, że jeśli instancje klasy Random zostaną utworzone z tym samym parametrem źródłowym i na instancjach zostaną wykonane te same sekwencje działań, zwrócą one identyczne sekwencje liczb. A jeśli przyjrzymy się uważnie, zobaczymy, że Random faktycznie ma konstruktora , który jako zarodek przyjmuje jakąś długą wartość :
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
Ten przykład zwróci wartość true , ponieważ materiał siewny obu instancji jest taki sam. Co robić? Domyślny konstruktor częściowo rozwiązuje problem. Poniżej znajduje się przykład zawartości konstruktora Random :
public Random() {
	this(seedUniquifier() ^ System.nanoTime());
}
Konstruktor domyślny używa bitowej operacji OR na wyłączność . I używa long reprezentującego bieżący czas i trochę materiału siewnego do tego :
private static long seedUniquifier() {
	for (;;) {
		long current = seedUniquifier.get();
		long next = current * 181783497276652981L;
		if (seedUniquifier.compareAndSet(current, next))
			return next;
	}
}
Kolejną interesującą rzeczą jest to, że każde wywołanie metody pobierającej nasionUniquifier zmienia wartość metody nasionUniquifier . Oznacza to, że klasa ma na celu możliwie najskuteczniejsze wybieranie liczb losowych. Jednak, jak mówi dokumentacja, „ nie są one bezpieczne kryptograficznie ”. Oznacza to, że do niektórych zastosowań w celach kryptograficznych (generowanie haseł itp.) nie jest odpowiedni, ponieważ przewidywana jest sekwencja z właściwym podejściem. Przykłady na ten temat można znaleźć w Internecie, na przykład tutaj: „ Przewidywanie następnego Math.random() w Javie ”. Lub na przykład kod źródłowy tutaj: „ Vulnerability Weak Crypto ”. Java.util.Random (generator liczb losowych) ma pewien „skrót” , czyli skróconą wersję wywołania, które jest wykonywane poprzez Math.random:
public static void main(String []args){
	int random_number = 1 + (int) (Math.random() * 6);
	System.out.println("Value: " + random_number);
}
Ale jeśli przyjrzysz się uważnie, w środku znajduje się ten sam Random:
public static double random() {
	return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
	static final Random randomNumberGenerator = new Random();
}
JavaDoc zaleca używanie klasy SecureRandom dla „ kryptograficznie bezpiecznego generatora liczb pseudolosowych ”.
Teoria prawdopodobieństwa w praktyce czyli czy znasz Random - 3

Bezpieczna losowa Java

Klasa SecureRandom jest podklasą java.util.Random i znajduje się w pakiecie java.security . Porównanie tych dwóch klas można przeczytać w artykule „ Różnica między java.util.Random a java.security.SecureRandom ”. Dlaczego ten SecureRandom jest tak dobry? Faktem jest, że dla niego źródłem liczb losowych jest coś tak magicznie brzmiącego jak „pula entropii rdzenia”. Jest to zarówno plus, jak i minus. O wadach tego możesz przeczytać w artykule: „ Niebezpieczeństwa związane z java.security.SecureRandom ”. Krótko mówiąc, Linux ma generator liczb losowych w jądrze (RNG). RNG generuje liczby losowe na podstawie danych z puli entropii, która jest wypełniana na podstawie losowych zdarzeń w systemie, takich jak taktowanie klawiatury i dysku, ruchy myszy, przerwania i ruch sieciowy. Więcej informacji na temat puli entropii opisano w materiale „ Liczby losowe w systemie Linux (RNG) czyli jak „wypełnić” /dev/random i /dev/urandom . W systemach Windows używany jest SHA1PRNG zaimplementowany w sun.security.provider.SecureRandom. Wraz z rozwojem Javy zmienił się także SecureRandom, o czym warto przeczytać w recenzji „ Aktualizacje Java SecureRandom od kwietnia 2016 r. ”, aby uzyskać pełny obraz.
Teoria prawdopodobieństwa w praktyce czyli czy znasz Random - 4

Wielowątkowość lub bądź jak Cezar

Jeśli spojrzysz na kod klasy Random , nic nie wskazuje na kłopoty. Metody nie są oznaczone jako zsynchronizowane . Jest jednak jedno ALE: tworząc Random z domyślnym konstruktorem w kilku wątkach, udostępnimy między nimi to samo ziarno instancji , dzięki któremu zostanie utworzony Random . A także po otrzymaniu nowej liczby losowej zmienia się również wewnętrzny AtomicLong instancji . Z jednej strony nie ma w tym nic złego z logicznego punktu widzenia, bo... Używany jest AtomicLong . Z drugiej strony za wszystko, także za produktywność, trzeba płacić. I za to też. Dlatego nawet oficjalna dokumentacja java.util.Random mówi: " Instancje java.util.Random są bezpieczne dla wątków. Jednakże jednoczesne użycie tej samej instancji java.util.Random w różnych wątkach może spowodować konflikty, a w konsekwencji słabą wydajność. Weź pod uwagę zamiast tego używaj ThreadLocalRandom w projektach wielowątkowych ”. Oznacza to, że w aplikacjach wielowątkowych, gdy aktywnie korzystamy z Random z kilku wątków, lepiej jest użyć klasy ThreadLocalRandom . Jego użycie różni się nieco od zwykłego losowego :
public static void main(String []args){
	int rand = ThreadLocalRandom.current().nextInt(1,7);
	System.out.println("Value: " + rand);
}
Jak widać, nie określamy dla niego materiału siewnego . Ten przykład opisano w oficjalnym samouczku firmy Oracle: Concurrent Random Numbers . Więcej o tej klasie przeczytasz w recenzji: „ Przewodnik po ThreadLocalRandom w Javie ”.
Teoria prawdopodobieństwa w praktyce czyli czy znasz Random - 5

StreamAPI i losowe

Wraz z wydaniem Java 8 mamy wiele nowych funkcji. W tym Stream API. Zmiany wpłynęły również na generowanie wartości losowych . Na przykład klasa Random posiada nowe metody, które pozwalają uzyskać Stream z losowymi wartościami, takimi jak int, doublelub long. Na przykład:
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));
    }
}
Dostępna jest także nowa klasa 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));
    }
}
Więcej o różnicach pomiędzy SplittableRandom i innymi klasami możesz przeczytać tutaj: „ Różne sposoby tworzenia liczb losowych w Javie ”.

Wniosek

Myślę, że warto wyciągnąć wnioski. Musisz uważnie przeczytać JavaDoc dla używanych klas. Za czymś tak prostym na pierwszy rzut oka jak Random kryją się niuanse, które mogą stanowić okrutny żart. #Wiaczesław
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION