JavaでのThreadLocalRandomのガイド

JavaのThreadLocalRandomのガイド

1. 概要

ランダム値の生成は非常に一般的なタスクです。 これが、Javaがjava.util.Randomクラスを提供する理由です。

ただし、このクラスはマルチスレッド環境ではうまく機能しません。

簡単に言うと、マルチスレッド環境でRandomのパフォーマンスが低下する理由は、複数のスレッドが同じRandomインスタンスを共有している場合、競合が原因です。

その制限に対処するには、Java introduced the java.util.concurrent.ThreadLocalRandom class in JDK 7 – for generating random numbers in a multi-threaded environment

ThreadLocalRandomのパフォーマンスと、実際のアプリケーションでの使用方法を見てみましょう。

2. Randomを超えるThreadLocalRandom

ThreadLocalRandom is a combination of ThreadLocal and Random classes, which is isolated to the current thread.したがって、Randomオブジェクトへの同時アクセスを回避するだけで、マルチスレッド環境でのパフォーマンスが向上します。

一方のスレッドで取得された乱数は、もう一方のスレッドの影響を受けませんが、java.util.Randomはグローバルに乱数を提供します。

また、Random,とは異なり、ThreadLocalRandomはシードの明示的な設定をサポートしていません。 代わりに、Randomから継承されたsetSeed(long seed)メソッドをオーバーライドして、呼び出された場合は常にUnsupportedOperationExceptionをスローします。

次に、ランダムなint, longdoubleの値を生成するいくつかの方法を見てみましょう。

3. ThreadLocalRandomを使用したランダム値の生成

Oracleのドキュメントによると、we just need to call ThreadLocalRandom.current() method, and it will return the instance of ThreadLocalRandom for the current thread。 その後、クラスの使用可能なインスタンスメソッドを呼び出すことにより、ランダムな値を生成できます。

境界のないランダムなint値を生成しましょう。

int unboundedRandomValue = ThreadLocalRandom.current().nextInt());

次に、ランダムに制限されたint値、つまり指定された下限と上限の間の値を生成する方法を見てみましょう。

0〜100のランダムなint値を生成する例を次に示します。

int boundedRandomValue = ThreadLocalRandom.current().nextInt(0, 100);

0は包括的下限であり、100は排他的上限であることに注意してください。

上記の例に示したのと同様の方法でnextLong()およびnextDouble()メソッドを呼び出すことにより、longおよびdoubleのランダム値を生成できます。

Java 8はまた、nextGaussian()メソッドを追加して、ジェネレーターのシーケンスから平均0.0および標準偏差1.0の次の正規分布値を生成します。

Randomクラスと同様に、doubles(), ints()およびlongs()メソッドを使用してランダムな値のストリームを生成することもできます。

4. JMHを使用したThreadLocalRandomRandomの比較

2つのクラスを使用して、マルチスレッド環境でランダムな値を生成する方法を見てから、JMHを使用してそれらのパフォーマンスを比較してみましょう。

まず、すべてのスレッドがRandom.の単一インスタンスを共有している例を作成しましょう。ここでは、Randomインスタンスを使用してランダム値を生成するタスクをExecutorService:に送信しています。

ExecutorService executor = Executors.newWorkStealingPool();
List> callables = new ArrayList<>();
Random random = new Random();
for (int i = 0; i < 1000; i++) {
    callables.add(() -> {
         return random.nextInt();
    });
}
executor.invokeAll(callables);

JMHベンチマークを使用して、上記のコードのパフォーマンスを確認しましょう。

# Run complete. Total time: 00:00:36
Benchmark                                            Mode Cnt Score    Error    Units
ThreadLocalRandomBenchMarker.randomValuesUsingRandom avgt 20  771.613 ± 222.220 us/op

同様に、プール内のスレッドごとにThreadLocalRandomの1つのインスタンスを使用するRandomインスタンスの代わりにThreadLocalRandomを使用しましょう。

ExecutorService executor = Executors.newWorkStealingPool();
List> callables = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
    callables.add(() -> {
        return ThreadLocalRandom.current().nextInt();
    });
}
executor.invokeAll(callables);

ThreadLocalRandom:を使用した結果は次のとおりです

# Run complete. Total time: 00:00:36
Benchmark                                                       Mode Cnt Score    Error   Units
ThreadLocalRandomBenchMarker.randomValuesUsingThreadLocalRandom avgt 20  624.911 ± 113.268 us/op

最後に、RandomThreadLocalRandomの両方について上記のJMHの結果を比較すると、Randomを使用して1000個のランダム値を生成するのにかかる平均時間は772マイクロ秒であるのに対し、Randomを使用すると明らかにわかります。 t3)sそれは約625マイクロ秒です。

したがって、ThreadLocalRandom is more efficient in a highly concurrent environmentと結論付けることができます。

JMHの詳細については、以前のarticle hereを確認してください。

5. 結論

この記事では、java.util.Randomjava.util.concurrent.ThreadLocalRandomの違いについて説明しました。

また、マルチスレッド環境でのRandomに対するThreadLocalRandomの利点、パフォーマンス、およびクラスを使用してランダムな値を生成する方法も確認しました。

ThreadLocalRandomはJDKへの単純な追加ですが、並行性の高いアプリケーションに顕著な影響を与える可能性があります。

そして、いつものように、これらすべての例の実装はGitHub projectにあります。