ThreadLocalRandomの正しい使い方


JAvaには擬似ランダムジェネレータとセキュリティ型の2種類の乱数ジェネレータがあり、擬似ランダムジェネレータは特定の式に基づいてseedを新しい擬似ランダムデータの一部に変換し、セキュリティランダムジェネレータはオペレーティングシステムが提供するランダムイベントに下位層で依存してデータを生成する.
セキュアランダムジェネレータ
  • 暗号化性の強いランダムデータを生成する必要がある場合に使用する.
  • 生成速度が遅い
  • 大量のランダムデータを生成する必要がある場合、ブロックが発生する可能性があり、外部割り込みイベント
  • を待つ必要がある.
    一方、擬似ランダムジェネレータは、「seed」の初期値にのみ依存し、生成アルゴリズムに同じseedを提供すれば、同じ擬似ランダムシーケンスを得ることができる.一般に、計算が密集している(IOデバイスに依存しない)ため、生成速度が速くなります.以下は擬似ランダムジェネレータの進化史である.
    java.util.Random  1.0から既に存在するスレッドセキュリティクラスであり、理論的には複数のスレッドで互いに異なる乱数を同時に得ることができ、このようなスレッドセキュリティはAtomicLongによって実現される.  RandomはAtomicLong CAS(compare and set)操作を使用してseedを更新し、多くの非ブロックアルゴリズムで非ブロック原語が使用されているにもかかわらず、CASのリソースの高度な競争時の表現は依然として悪い.
    java.util.concurrent.ThreadLocalRandom  1.7クラスを追加し、すべてのパフォーマンスの問題を克服するためにRandomと結合しようとします.クラスはRandomから継承されます.
    Randomはスレッドセキュリティの問題を解決するためにcompareAndSet+synchronizedを使用し、ThreadLocalを使用して競合を回避できますが、synchronized/compareAndSetによるオーバーヘッドは回避できません.性能を考慮するとやはりThreadLocalRandom(3倍以上アップ)を置き換えることをお勧めしますが、これはThreadLocalパッケージ後のRandomではなく、実際にThreadLocalメカニズムを使って再実現したRandomです.
    ThreadLocalRandomの主な実装の詳細:
  • 通常のlongを使用し、RandomのAtomicLongをseed
  • として使用するのではなく、
  • ThreadLocalRandomインスタンスを自分で作成することはできません.構造関数はプライベートなので、静的ファクトリThreadLocalRandom.current()
  • を使用することができます.
  • CPUキャッシュセンシング式である、64ビットL 1キャッシュ行
  • を8つのlong仮想ドメインで満たす.
    ThreadLocalRandom不適切なマルチスレッドを使用して同じ乱数を生成
    import java.util.concurrent.ThreadLocalRandom;
    
    public class ThreadLocalRandomDemo {
    
        private static final ThreadLocalRandom RANDOM =
                ThreadLocalRandom.current();
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Player().start();
            }
        }
    
        private static class Player extends Thread {
            @Override
            public void run() {
                System.out.println(getName() + ": " + RANDOM.nextInt(100));
            }
        }
    }

    このコードを実行すると、次のようになります.
    Thread-0: 4
    Thread-1: 4
    Thread-2: 4
    Thread-3: 4
    Thread-4: 4
    Thread-5: 4
    Thread-6: 4
    Thread-7: 4
    Thread-8: 4
    Thread-9: 4

    理由は次のとおりです.
    ThreadLocalRandomを初期化したメインスレッドが取得したランダム値はモードなし(呼び出し元は次の戻り値を予測できず、擬似ランダムに対する要求を満たす)である以外は、他のスレッドが取得したランダム値は互いに独立していない(本質的には、乱数を生成するために使用されるシードseedの値が予測可能であり、i*gammaであり、iは現在のスレッドが乱数生成方法を呼び出した回数であり、gammaはThreadLocalRandomクラスのlong静的フィールド値である)たとえば、ThreadLocalRandomインスタンスを初期化していないすべてのスレッドが同じ回数のnextInt()メソッドを呼び出すと、その結果得られる乱数列は完全に同じであるという興味深い現象があります.この現象の原因は、ThreadLocalRandomクラスがクラス単一例フィールドを維持し、スレッドがThreadLocalRandom#current()を呼び出すことによってメソッドを使用してThreadLocalRandomの単一例を取得し、スレッドメンテナンスのインスタンスフィールドthreadLocalRandomSeedをシードとして次の乱数と次のシード値を生成します.では、単一例モードである以上、マルチスレッド共通マスタースレッド初期化のインスタンスに問題が発生するのはなぜかということです.問題は、currentメソッドでスレッドがcurrent()を呼び出していることですメソッドの場合、各スレッドのthreadのインスタンスフィールドthreadLocalRandomProbeが0であるか否かに基づいて、現在のスレッドインスタンスが最初の呼び出し乱数生成メソッドであるか否かを判断し、現在のスレッドにランダムなthreadLocalRandomSeedシード値を初期化するか否かを決定する.したがって、他のスレッドがcurrentメソッドを迂回して直接呼び出す乱数法では,その種子値は0,1*gamma,2*gamma...であるため予測可能である.
     正しい使い方:
    ThreadLocalRandomの正しい使い方はThreadLocalRandom.current().nextX(…)であり、マルチスレッド間でThreadLocalRandomを共有することはできません
    import java.util.concurrent.ThreadLocalRandom;
    
    public class ThreadLocalRandomDemo {
    
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++) {
                new Player().start();
            }
        }
    
        private static class Player extends Thread {
            @Override
            public void run() {
                System.out.println(getName() + ": " + ThreadLocalRandom.current().nextInt(100));
            }
        }
    }