首頁  >  文章  >  Java  >  關於java.util.Random實現的方法講解及原理介紹

關於java.util.Random實現的方法講解及原理介紹

巴扎黑
巴扎黑原創
2017-09-08 09:50:313339瀏覽

Java實用工具類別庫中的類別java.util.Random提供了產生各種類型隨機數的方法,以下這篇文章主要給大家介紹了關於java.util.Random實現原理的相關資料,文中通過範例程式碼介紹的非常詳細,需要的朋友可以參考下。

概述

java.util.Random可以產生int、long、float、double以及Goussian等型別的隨機數。這也是它與java.lang.Math中的方法Random()最大的不同之處,後者只產生double型的隨機數。

該類別的實例被用來產生偽隨機數的流。這類使用一個 48 位元的種子,它被一個線性同餘公式所修改。如果 Random 的兩個實例以同一種子創建,則對每個實例完成同方法呼叫序列它們將產生和傳回相同的數序列成同一方法呼叫序列,它們將產生和傳回相同的數序列。

範例


#
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("");
 }
}

輸出結果:


從結果發現,只要種子一樣,所獲得的隨機數的序列就是一致的。是一種偽隨機數的實現,而不是真正的隨機數。

Random 原始碼分析

Random 類別結構


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);

有參考建構方法


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;
}

透過傳入一個種子,來產生隨機數,透過上面的例子發現,種子一樣產生的隨機數序列一樣,如果每次使用想產生不一樣的序列,那隻能每次傳入一個不一樣的種子。

無參建方法


#
public Random() {
 this(seedUniquifier() ^ System.nanoTime());
 }
private static long seedUniquifier() {
 // L&#39;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;
 }
}

透過原始碼發現,無參的建構方法,裡面幫我們自動產生了一個種子,並透過CAS自旋方式保證,每次取得的種子都不一樣,從而保證每次new Random()所獲得的隨機序列不一致。

nextInt() 方法:取得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));
}

從程式碼我們可以發現,只要種子確定後,每次產生的數,都是採用固定的演算法進行產生的,所以只要種子確定後,每次產生的序列就是固定的。

每次更新種子的時候是使用的CAS來更新的,如果高並發的環境下,性能是個問題。

安全性問題

試想下,如果這是一個搖獎平台,只要種子確定後,每次產生的序列都一樣。這樣就可利用這個漏洞來預測下一次開獎的號碼,這樣容易被一些人鑽空子。

jdk建議大家盡量使用 SecureRandom 來實現隨機數的產生。

SecureRandom

SecureRandom是強隨機數產生器,主要應用的場景為:用於安全目的的資料數,例如產生秘鑰或會話標示(session ID),在上文《偽隨機數安全性》中,已經向大家揭露了弱隨機數產生器的安全問題,而使用SecureRandom這樣的強隨機數產生器將會極大的降低出問題的風險。

產生高強度的隨機數,有兩個重要的因素:種子和演算法。演算法是可以有很多的,通常如何選擇種子是非常關鍵的因素。 如Random,它的種子是System.currentTimeMillis(),所以它的隨機數都是可預測的, 是弱偽隨機數。
強偽隨機數的生成思路:收集計算機的各種信息,鍵盤輸入時間,內存使用狀態,硬碟空閒空間,IO延時,進程數量,線程數量等信息,CPU時鐘,來得到一個近似隨機的種子,主要是達到不可預測性。

說的簡單點就是,使用加密演算法產生很長的一個隨機種子,讓你無法猜測種子,也就無法推導出隨機序列數。

Random效能問題

從Random 原始碼中我們發現,每次取得隨機數的時候都是使用CAS的方式進行更新種子的值。這樣在高併發的環境中會存在大量的CAS重試,導致效能下降。這時建議大家使用ThreadLocalRandom類別來實現隨機數的產生。

ThreadLocalRandom 實作原則

#Thread 類別

# Thread 類別中有一個threadLocalRandomSeed 屬性。

ThreadLocalRandom 結構

#

SEED 變數是 threadLocalRandomSeed 在 Thread 物件中的偏移量。

ThreadLocalRandom.nextSeed() 方法

從這個方法中,我們發現,每個執行緒的種子值都儲存在Thread物件的threadLocalRandomSeed 屬性中。

結論

因為ThreadLocalRandom 中的種子儲存在Thread物件中,所以高並發取得Random物件時,不會使用CAS來保證每次獲取的值不一致。
每個線程維護一個它自己的種子,每個線程需要獲取隨機數的時候,從當前的Thread對像中獲取當前線程的種子,進行獲取隨機數,性能大大提高。

以上是關於java.util.Random實現的方法講解及原理介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn