導入
世界には確率論を研究する科学がたくさんあります。そして科学はさまざまなセクションで構成されています。たとえば、数学には、ランダムな出来事や数量などの研究に特化した別のセクションがあります。しかし、科学は軽視されているわけではありません。この場合、確率論は、人々が運任せのゲームをするときにサイコロを振るパターンがあることを理解しようとしたときに形を作り始めました。よく見てみると、私たちの周りには一見ランダムに見えるものがたくさんあります。しかし、ランダムなものはすべて完全にランダムというわけではありません。しかし、それについては後で詳しく説明します。Java プログラミング言語では、JDK の最初のバージョンから乱数もサポートされています。
Java の乱数は、 java.util.Randomクラスを使用して使用できます。
テストには、チュートリアルポイント 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クラスは擬似乱数を使用します。どうして?乱数はそれほどランダムではないことがわかりましたか?
擬似乱数 java.util.Random
java.util.Randomクラスのドキュメントには、同じ
シードパラメータを使用してRandom のインスタンスが作成され、そのインスタンスに対して同じ一連のアクションが実行されると、同じ一連の数値が返されると記載されています。
そして、よく見てみると、 Random には実際に
長い値を
シードとして受け取る
コンストラクターがあることがわかります。
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());
}
デフォルトのコンストラクターは、ビット単位の排他的
OR演算を使用します。そして、このために、現在の時刻を表す
longといくつかのシードを使用します。
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
ここでのもう 1 つの興味深い点は、 seedUniquiifier getter メソッド を呼び出すたびに、
seedUniquiifier の値が変更されることです。つまり、クラスは可能な限り効率的に乱数を選択するように設計されています。ただし、ドキュメントに記載されているように、それらは「
暗号的に安全ではありません」。つまり、暗号化目的の一部の使用目的 (パスワード生成など) には適していません。適切なアプローチによるシーケンスが予測されます。このトピックに関する例はインターネット上にあります。たとえば、「
Java での次の Math.random() の予測」です。または、たとえば、ここにあるソース コード:「
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クラスを使用することを推奨しています。
安全なランダム Java
SecureRandomクラスは
java.util.Randomのサブクラスであり、
java.securityパッケージにあります。これら 2 つのクラスの比較については、記事「
java.util.Random と java.security.SecureRandom の違い」を参照してください。なぜこの SecureRandom がそれほど優れているのでしょうか? 実際のところ、彼にとって乱数の源は「コア エントロピー プール」のような魔法のように聞こえるものです。これはプラスでもありマイナスでもあります。この欠点については、記事「
java.security.SecureRandom の危険性」を参照してください。つまり、Linux にはカーネル乱数生成器 (RNG) が備わっています。RNG は、エントロピー プールのデータに基づいて乱数を生成します。エントロピー プールは、キーボードとディスクのタイミング、マウスの動き、割り込み、ネットワーク トラフィックなど、システム内のランダム イベントに基づいて満たされます。エントロピー プールの詳細については、資料「
Linux の乱数 (RNG) または /dev/random および /dev/urandom を「埋める」方法」で説明されています。Windows システムでは、sun.security.provider.SecureRandom に実装された SHA1PRNG が使用されます。Java の開発に伴い、SecureRandom も変更されました。その全体像については、「
2016 年 4 月時点の Java SecureRandom の更新」のレビューを読む価値があります。
マルチスレッドかシーザーのようになるか
Randomクラスのコードを見ると、問題を示すものは何もないようです。
メソッドにはsynchronized のマークが付けられません。ただし、1 つ注意があります。複数のスレッドでデフォルトのコンストラクターを使用して
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 の公式チュートリアル「
同時乱数」で説明されています。このクラスの詳細については、レビュー「
Java での ThreadLocalRandom のガイド」を参照してください。
StreamAPIとランダム
Java 8 のリリースにより、多くの新機能が追加されました。ストリームAPIを含む。
そして、この変更はランダム値の生成にも影響を与えました。
たとえば、 Randomクラスには、 、、 などのランダムな値を持つ
Streamを取得できる新しいメソッドがあります。例えば:
int
double
long
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 を注意深く読む必要があります。「ランダム」のように一見単純なものの背後には、残酷なジョークを演じる可能性のあるニュアンスがあります。#ヴィアチェスラフ
GO TO FULL VERSION