Java中生成隨機數Random、ThreadLocalRandom、SecureRandom、Ma

我們來說說Java常見的生成隨機數的幾種方式:Random,ThreadLocalRandom,SecureRandom;其實產生隨機數有很多種方式但我們常見的就這幾種,如果需要詳細瞭解這個三個類,可以查看JAVA API.

Random random = new Random();
int a = random.nextInt(5);//隨機生成0~4中間的數字
其實Random是有構造函數的,他的參數可以傳一個long類型的值,當使用空的構造的時候,使用的實際上是System.nanoTime()也就是當前時間毫秒數的值,我們把這個叫做 種子 。

種子是幹什麼的呢,實際上我們生成的隨機數都是僞隨機數,而想要使我們生成的隨機數強度更高,就需要更好的 算法 和種子。一般情況下,要使用Random去生成隨機數,直接用空構造函數就可以了。那麼這個種子到底有什麼用呢,實際上讀者去試驗一下就知道了,我們使用固定隨機數,比如1,然後我們連續次去new這個Random,然後去生成一個隨機數,像下面這樣,你會發現,三個數的結果是一樣的。

/**
 * -1157793070
 * 1913984760
 * 1107254586
 */
@Test
public void test2(){
    Random random = new Random(10); 
    for (int i = 0; i < 3; i++){
        System.out.println(random.nextInt());
    }
}

所以我們一定不能把這個種子寫死,用當前時間毫秒數,還是比較好些。另外,使用Random儘量不要重複new對象,其實也沒什麼意義的。最後說一點,Random是線程安全的,去 這裏 的官方文檔可以看到,“Instances of java.util.Random are threadsafe.”。但是在 多線程的表現中,他的性能很差。

在Java的API幫助文檔中,總結了一下對這個Random的描述:

java.util.Random類中實現的隨機算法是僞隨機,也就是有規則的隨機,所謂有規則的就是在給定種(seed)的區間內隨機生成數字;
相同種子數的Random對象,相同次數生成的隨機數字是完全相同的;

Random類中各方法生成的隨機數字都是均勻分佈的,也就是說區間內部的數字生成的機率均等;

這個類是Java7新增的類,給多線程併發生成隨機數使用的。爲什麼ThreadLocalRandom要比Random快呢,這是因爲Random在生成隨機數的時候使用了CAS(compare and set),但是ThreadLocalRandom卻沒有使用。

下面是java.util.Random的生成隨機數的方法:

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));
}
而這邊的seed是一個全局變量:

/**

  • The internal state associated with this pseudorandom number generator.
  • (The specs for the methods in this class describe the ongoing
  • computation of this value.)
    */
    private final AtomicLong seed;
    多個線程同時獲取隨機數的時候,會競爭同一個seed,導致了效率的降低。

可見,其中通過CAS方式保證其線程安全性。這在高併發的環境中由於線程間的競爭必然帶來一定的性能損耗。

ThreadLocal此時就派上用場了,ThreadLocalRandom是通過ThreadLocal改進的用於隨機數生成的工具類,每個線程單獨持有一個ThreadLocalRandom對象引用,這就完全杜絕了線程間的競爭問題。

另外ThreadLocalRandom的實例化比較特別,下面簡單舉例一下。

ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
int a = threadLocalRandom.nextInt(5);
由於是和線程綁定的,所以他也是從當前線程獲取的。

在需要頻繁生成隨機數,或者安全要求較高的時候,不要使用Random,這個很好理解吧,從我們最開始的介紹中可以知道,Random生成的值其實是可以預測的。

內置兩種隨機數算法,NativePRNG和SHA1PRNG,看實例化的方法了。通過new來初始化,默認來說會使用NativePRNG算法生成隨機數,但是也可以配置-Djava.security參數來修改調用的算法。如果是/dev/[u]random兩者之一就是NativePRNG,否則就是SHA1PRNG。

在 jvm 啓動參數這樣加就好了,-Djava.security=file:/dev/urandom。

當然還可以通過getInstance來初始化對象,有一個參數的,直接傳一個算法名就行,如果不存在算法拋異常;另外有兩個參數的,第二個參數還可以指定算法程序包。下面來看下實現代碼。

SecureRandom secureRandom = new SecureRandom();
SecureRandom secureRandom3 = SecureRandom.getInstance("SHA1PRNG");
SecureRandom secureRandom2 = SecureRandom.getInstance("SHA1PRNG", "SUN");
當然我們使用這個類去生成隨機數的時候,一樣只需要生成一個實例每次去生成隨機數就好了,也沒必要每次都重新生成對象。另外,這個類生成隨機數,首次調用性能比較差,如果條件允許最好服務啓動後先調用一下nextInt()。

另外,實際上SHA1PRNG的性能將近要比NativePRNG的性能好一倍,synchronized的代碼少了一半,所以沒有特別重的安

全需要,儘量使用SHA1PRNG算法生成隨機數。

這也是個比較常用的生成隨機數的方式,默認生成0~1之間的小數.

總結:

1、單機中如果對安全性要求不高的情況下,使用 Random;對安全性要求高,就用 SecureRandom;

SecureRandom裏有兩種算法,SHA1PRNG 和 NativePRNG,SHA1PRNG的性能好,但是NativePRNG的安全性高。

2、Random 是線程安全的,用CAS來保持,但是性能比不高,所以多線程中,儘量使用 java併發包裏的 ThreadLocalRandom,

避免了線程之間的競爭導致的性能問題

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章