JavaRush /Java Blog /Random-TW /實踐中的機率論或您了解隨機嗎
Viacheslav
等級 3

實踐中的機率論或您了解隨機嗎

在 Random-TW 群組發布
實踐中的機率論或您了解隨機 - 1

介紹

世界上有許多科學研究機率論。科學由不同的部分組成。例如,在數學中,有一個單獨的部分專門研究隨機事件、數量等。但科學並沒有被輕視。在這種情況下,當人們試圖了解玩機會遊戲時擲骰子的模式時,機率論就開始成形。如果你仔細觀察,我們周圍有很多看似隨機的事情。但一切隨機的事情並不完全隨機。但稍後會詳細介紹。從 JDK 的第一個版本開始,Java 程式語言也支援隨機數。Java 中的隨機數可以透過java.util.Random類別來使用。為了進行測試,我們將使用tutorialspoint java線上編譯器以下是使用Random來模擬投擲「骰子」(俄語中的立方體) 的原始範例:
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);
    }
}
看起來對Random的描述到此就結束了,但事實並非如此簡單。我們打開Java API中java.util.Random類別的描述。在這裡我們看到了有趣的事。Random類別使用偽隨機數。為何如此?原來隨機數並沒有那麼隨機?
實踐中的機率論或您了解隨機 - 2

偽隨機性 java.util.Random

java.util.Random類別的文件表示,如果使用相同的種子參數建立Random實例並對實例執行相同的操作序列,則它們將傳回相同的數字序列。如果我們仔細觀察,我們可以看到Random實際上有一個構造函數,它以一些long值作為種子:
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
這個例子將會回傳true因為 兩個實例的種子是相同的。怎麼辦?預設構造函數部分解決了這個問題。以下是Random建構函數的內容範例:
public Random() {
	this(seedUniquifier() ^ System.nanoTime());
}
預設構造函數使用位元異運算。並使用代表當前時間的long一些種子:
private static long seedUniquifier() {
	for (;;) {
		long current = seedUniquifier.get();
		long next = current * 181783497276652981L;
		if (seedUniquifier.compareAndSet(current, next))
			return next;
	}
}
這裡另一個有趣的事情是,每次呼叫SeedUniquifier getter 方法都會更改SeedUniquifier 的值。也就是說,該類別被設計為盡可能有效地選擇隨機數。然而,正如文件所說,它們「在加密上並不安全」。也就是說,對於某些用於加密目的(密碼產生等)的用途來說,它是不適合的,因為 使用正確方法預測序列。網路上有關於此主題的範例,例如:「Predicting the next Math.random() in Java」。或例如這裡的源代碼:“ Vulnerability Weak Crypto ”。java.util.Random (隨機數產生器)有一個特定的“捷徑”,透過 Math.random 執行的呼叫的縮短版本:
public static void main(String []args){
	int random_number = 1 + (int) (Math.random() * 6);
	System.out.println("Value: " + random_number);
}
但仔細觀察,你會發現裡面有相同的隨機數字:
public static double random() {
	return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
	static final Random randomNumberGenerator = new Random();
}
JavaDoc 建議使用SecureRandom類別作為「加密安全偽隨機數產生器」。
實踐中的機率論或您了解隨機 - 3

安全隨機 Java

SecureRandom類是java.util.Random的子類,位於java.security套件中。這兩個類別的比較可以閱讀文章「java.util.Random 和 java.security.SecureRandom 之間的差異」。為什麼這個 SecureRandom 這麼好?事實上,對他來說,隨機數的來源就是「核心熵池」這樣一個聽起來很神奇的東西。這既是優點也是缺點。您可以在文章“ java.security.SecureRandom 的危險”中了解其缺點。簡而言之,Linux 有一個核心隨機數產生器(RNG)。RNG 根據熵池中的資料產生隨機數,熵池根據系統中的隨機事件填充,例如鍵盤和磁碟計時、滑鼠移動、中斷和網路流量。有關熵池的更多信息,請參閱材料“ Linux 中的隨機數 (RNG) 或如何“填充”/dev/random 和 /dev/urandom ”。在 Windows 系統上,使用 SHA1PRNG,在 sun.security.provider.SecureRandom 中實作。隨著 Java 的發展,SecureRandom 也發生了變化,值得閱讀評論「截至 2016 年 4 月的 Java SecureRandom 更新」以了解完整情況。
實踐中的機率論或您了解隨機 - 4

多線程或者像凱撒一樣

如果您查看Random類的程式碼,似乎沒有任何跡象表明有問題。方法未標記為同步。但有一個 BUT:當在多個執行緒中使用預設建構函式建立Random時,我們將在它們之間共享相同的實例種子,透過該種子來建立Random。而當收到新的隨機數時,實例的內部AtomicLong也會改變。一方面,從邏輯的角度來看,這並沒有什麼問題,因為… 使用AtomicLong。另一方面,你必須為一切付出代價,包括生產力。也是為了這個。因此,即使java.util.Random的官方文件也說:「java.util.Random 的實例是線程安全的。但是,跨線程並發使用同一個java.util.Random 實例可能會遇到爭用,從而導致效能不佳。考慮相反,在多線程設計中使用 ThreadLocalRandom」。也就是說,在多執行緒應用程式中,當從多個執行緒主動使用Random時,最好使用ThreadLocalRandom類別。它的用法與常規Random略有不同:
public static void main(String []args){
	int rand = ThreadLocalRandom.current().nextInt(1,7);
	System.out.println("Value: " + rand);
}
正如您所看到的,我們沒有為其指定種子。Oracle 的官方教學:Concurrent Random Numbers中描述了此範例。您可以在評論中閱讀有關此類的更多資訊:「Guide to ThreadLocalRandom in Java」。
實踐中的機率論或您了解隨機 - 5

StreamAPI 和隨機

隨著 Java 8 的發布,我們有了許多新功能。包括流API。這些變化也影響了隨機值的產生。例如, Random類別具有新方法,可讓您取得具有隨機值(如、或 )的Stream。例如: intdoublelong
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));
    }
}
還有一個新類別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));
    }
}
您可以在此處閱讀有關SplittableRandom和其他類別 之間的區別的更多資訊:「在 Java 中創建隨機數的不同方法」。

結論

我認為值得下結論。您需要仔細閱讀所使用的類別的 JavaDoc。像是「隨機」乍看之下很簡單的東西背後,隱藏著可能會開一個殘酷玩笑的細微差別。#維亞切斯拉夫
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION