ホームページ  >  記事  >  Java  >  java.util.Random実装の方法と原則の紹介

java.util.Random実装の方法と原則の紹介

巴扎黑
巴扎黑オリジナル
2017-09-08 09:50:313285ブラウズ

Java ユーティリティ クラス ライブラリの java.util.Random クラスは、さまざまな種類の乱数を生成するメソッドを提供します。次の記事では、主に java.util.Random の実装原理に関する関連情報を紹介します。紹介はとても詳しく書かれているので、困っている友達は参考にしてください。

概要

java.util.Randomは、int、long、float、double、Goussianなどの型の乱数を生成できます。これは、double 型の乱数のみを生成する java.lang.Math のメソッド Random() との最大の違いでもあります。

このクラスのインスタンスは、擬似乱数のストリームを生成するために使用されます。このクラスは、線形合同式によって変更された 48 ビット シードを使用します。 Random の 2 つのインスタンスが同じシードで作成された場合、各インスタンスで同じ一連のメソッド呼び出しを完了することにより、同じ一連の数値が生成されて返されます。


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

出力結果:


この結果から、シードが同じである限り、得られる乱数の系列は一貫していることがわかります。これは疑似乱数の実装であり、真の乱数ではありません。

ランダムソースコード解析

ランダムクラス構造


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 は、強力な乱数生成器です。 主なアプリケーション シナリオは次のとおりです。 秘密鍵やセッション ID の生成など、セキュリティ目的のデータ番号。弱い乱数ジェネレーターのセキュリティ問題は誰にでもさらされており、SecureRandom のような強力な乱数ジェネレーターを使用すると、問題のリスクが大幅に軽減されます。

強度の高い乱数を生成するには、シードとアルゴリズムという 2 つの重要な要素があります。多くのアルゴリズムが存在する可能性があり、通常、シードをどのように選択するかが非常に重要な要素になります。 たとえば、Random のシードは System.currentTimeMillis() であるため、その乱数は予測可能であり、弱い擬似乱数です。
強力な擬似乱数生成のアイデア: さまざまなコンピューター情報、キーボード入力時間、メモリ使用状況、ハードディスクの空き容量、IO 遅延、プロセス数、スレッド数およびその他の情報、CPU クロックを収集して、ほぼランダムなシードを取得します。 、主に予測不可能性を実現するためです。

簡単に言うと、暗号化アルゴリズムを使用して非常に長いランダム シードを生成します。これにより、シードを推測できなくなり、ランダム シーケンス番号を推測できなくなります。

ランダムのパフォーマンスの問題

ランダムのソースコードから、乱数を取得するたびに CAS を使用してシード値を更新していることがわかりました。このように、同時実行性の高い環境では CAS の再試行が多数発生し、パフォーマンスが低下します。現時点では、ThreadLocalRandom クラスを使用して乱数を生成することをお勧めします。

ThreadLocalRandom実装原理

Threadクラス

ThreadクラスにはthreadLocalRandomSeed属性があります。

スレッドローカルランダム構造

SEED 変数は、Thread オブジェクト内の threadLocalRandomSeed のオフセットです。

ThreadLocalRandom.nextSeed() メソッド

このメソッドから、各スレッドのシード値が Thread オブジェクトの threadLocalRandomSeed プロパティに格納されていることがわかります。

結論

ThreadLocalRandomのシードはThreadオブジェクトに格納されるため、同時実行性の高いRandomオブジェクトを取得する場合、毎回取得される値の不整合を保証するためにCASは使用されません。
各スレッドは独自のシードを保持し、乱数を取得する必要がある場合、現在の Thread オブジェクトから現在のスレッドのシードを取得し、パフォーマンスが大幅に向上します。

以上がjava.util.Random実装の方法と原則の紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。