RandomとThreadLocalRandomの実現原理


詳細
JDK 7から新しい疑似乱数生成器,ThreadLocalRandomを導入し,名前からスレッドに関連するRandomであることがわかり,以前のRandomと比較すると,ThreadLocalRandomは性能的にマルチスレッドとの同時処理にいくつかの改良を加えた.
 
1,sun.misc.Unsafe
疑似乱数を生成する過程で、RandomとThreadLocalRandomは特殊なパッケージ:sunを使用した.misc.Unsafe.まずこのパッケージについて簡単に紹介します.
      sun.misc.UnsafeはJDKソースコードに複数存在する.より効率的で簡単にするために、プラットフォームに関連するいくつかのより下位の機能、すなわちJNIのいくつかの簡単な機能の代替を提供し、Unsafeの大部分の方法はNative法である.名前からも分かるように、Javaの意味では「安全ではない」機能を提供しているため、Javaコアクラスライブラリ以外で使用すべきではありません.実際には,反射機構に加えて,その機能を用いてUnsafeのインスタンスを取得することはできない.
詳細については、以下を参照してください.http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe.
 
2,Randomを理解する:
Randomの説明から分かるように、1つのRandomクラスのインスタンスは、擬似乱数を生成するために使用することができる.擬似乱数を生成するプロセスは、seedを指定し、式によって新しいseedを生成し、新しいseedに基づいて擬似乱数に値する.一方、2つの異なるRandomインスタンスが指定されたseedと同じである場合、生成される擬似乱数も同じである.
コード表示:seedが同じであれば、生成された乱数は同じである.
public static void main(String[] args) {
		// TODO Auto-generated method stub
		int currentseed = 100;
		System.out.println("current seed:" + currentseed);
		Random r1 = new Random(currentseed);
		int value1 = r1.nextInt();
		
		Random r2 = new Random(currentseed);
		int value2 = r2.nextInt();
		
		System.out.println("Random Object: " + r1.hashCode() + " AND  " + r2.hashCode());
		
		System.out.println("get random int:" + value1 + "," + value2);
	}

   
実行結果は次のとおりです.
current seed:100
Random Object: 1311053135 AND  118352462
get random int:-1193959466,-1193959466

実行結果から,同じseed,異なるRandomオブジェクトで生成された乱数は同じであることが分かる.
 
       
複数のスレッドで複数のRandomインスタンスを使用する場合、指定したseedが同じである場合、生成される疑似乱数シーケンスも同じです.したがって、Randomのデフォルトのseedを使用する、または現在の時間Systemを使用するなど、異なるseedを使用してRandomインスタンスを作成する必要がある.currentTimeMillis();
コード表示:マルチスレッドでseedが同じであれば、生成された乱数は同じである.
public static void main(String[] args) {
		// TODO Auto-generated method stub
		int currentseed = 100;
		System.out.println("current seed:" + currentseed);
		
		RandomThread rThread1 = new RandomThread(currentseed);
		new Thread(rThread1).start();
		
		RandomThread rThread2 = new RandomThread(currentseed);
		new Thread(rThread2).start();
		
}

public class RandomThread implements Runnable {

	private int seed;
	
	
	public RandomThread(int seed) {
		super();
		this.seed = seed;
	}

	public void run() {
		// TODO Auto-generated method stub
		Random r1 = new Random(seed);
		try {
			for(int k =0 ;k<5;k++){
				int value1 = r1.nextInt();
				System.out.println("get random int" + k + ":" + value1);
				Thread.sleep(200);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

実行結果:
current seed:100
get random int0:-1193959466
get random int0:-1193959466
get random int1:-1139614796
get random int1:-1139614796
get random int2:837415749
get random int2:837415749
get random int3:-1220615319
get random int3:-1220615319
get random int4:-1429538713
get random int4:-1429538713

2つのスレッドでは2つのRandomをインスタンス化し、同じseedを使用すると、完全に同じ2つの乱数シーケンスが生成されます.
 
ランダム数のソースコードをRandomから取得するには:
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));
    }
private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;

を選択します.
nextseed = (oldseed * multiplier + addend) & mask;

このうちoldseedは伝達された初期seedである.他の3つのパラメータ、multiplier、addend、maskは、クラス静的変数です.したがって、異なるRandomインスタンスであっても、生成されたnextseed値は同じであり、得られる乱数は同じである.
 
 
したがって、乱数の効果を確実にするには、同じRandomインスタンスを複数のスレッドで使用することで、同じseed値のセットを維持するだけです.一方、Randomインスタンスが乱数を生成するときのseed値の更新は原子操作であるため、複数のスレッドがブロックされ、同時性に影響を与える可能性があります.
乱数を取得するときの最後の行の操作はseed値を更新する原子操作であり、AtomicLongクラスではunsafeを直接呼び出す.compareAndSwapLongメソッドが完了しました.
private final AtomicLong seed;
//seed final, seed , Java seed 。
 
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));
    }

//seed.compareAndSet(oldseed, nextseed)  , unsafe 。
public final boolean compareAndSet(long expect, long update) {
        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
    }

ここでseed.compareAndSet(oldseed,nextseed)は、unsafeのメソッドを呼び出して完了する.この方法はseedのメモリ位置オフセット量を取得することによってseedの値を設定し、oldseed値をnextseed値で置き換える.注意seed変数はfinalに設定されているため、Javaプログラムでseed値を再変更することはできません.ここでunsafeでnativeメソッドを呼び出してseed値の更新を完了します.
 
コードは複数のスレッドが同じrandomインスタンスを共有している場合、プログラムの結果からランダム数を取得する操作の一部があり、時間がかかることが多く、マルチスレッドが同じRandomを使用する場合、ブロックが発生しやすいことを示しています.
        
public static void main(String[] args) {
		// TODO Auto-generated method stub
		int currentseed = 100;
		Random currRandom = new Random(currentseed);
		for(int k=0;k<20;k++){
			RandomThread rThread1 = new RandomThread(currRandom);
			new Thread(rThread1).start();	
		}
	}



public class RandomThread implements Runnable {

	private Random currentRandom;
	
	public RandomThread(Random random) {
		super();
		this.currentRandom = random;
	}

	public void run() {
		// TODO Auto-generated method stub
		try {
			for(int k =0 ;k<10;k++){
				long currenttime = System.currentTimeMillis();
				int value1 = currentRandom.nextInt(50);
				System.out.println("get random int" + k + ":" + value1);
				System.out.println("Cost time for random value:" + (System.currentTimeMillis() - currenttime));
				Thread.sleep(100 * value1);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

 
 
以上の分析から、Randomに存在するいくつかの問題をまとめることができる.
複数のインスタンスを生成し、リソースを浪費する.
同じseed値で、生成された擬似乱数シーケンスは同じで、効果は理想的ではありません.
複数のスレッドが同じインスタンスを共有すると、同時パフォーマンスが低下します.
そのためThreadLocalRandomが誕生した.
 
3,理解ThreadLocalRandom:
Randomに存在するいくつかの問題に対して、ThreadLocalRandomは的確に改善された.ThreadLocalRandomは単一のモードであり、システムは1つのThreadLocalRandomインスタンスのみを保存する.
プログラムはcurrent(); ThreadLocalRandomインスタンスを取得します.
public static ThreadLocalRandom current() {
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
            localInit();
        return instance;
    }
/**
     * Initialize Thread fields for the current thread.  Called only
     * when Thread.threadLocalRandomProbe is zero, indicating that a
     * thread local seed value needs to be generated. Note that even
     * though the initialization is purely thread-local, we need to
     * rely on (static) atomic generators to initialize the values.
     */
    static final void localInit() {
        int p = probeGenerator.addAndGet(PROBE_INCREMENT);
        int probe = (p == 0) ? 1 : p; // skip 0
        long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
        Thread t = Thread.currentThread();
        UNSAFE.putLong(t, SEED, seed);
        UNSAFE.putInt(t, PROBE, probe);
    }

インスタンスを取得する場合、各スレッドに対して、ローカル変数のパラメータ初期化が最初に行われるだけで、複数のスレッドのseed値も異なります.したがって,複数のスレッドがThreadLocalRandomを使用すると,得られる乱数も異なる.
        
ただし、ThreadLocalRandomはスレッドごとに変数値をローカルメモリに保存し、最初の使用時に各変数値を初期化し、各変数に値を付与します.
seed値を更新する場合も、ローカルメモリを操作し、マルチスレッド操作を回避すると、変数値の同期が大幅に向上し、同時性能が大幅に向上します.
ThreadLocalRandomではいくつかの変数が定義されていますが、これらの変数の値は、Threadスレッドによってローカルに保存されます.つまり、各スレッドに変数が保存されています.
    //ThreadLocalRandom , Unsafe, 。
    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long SEED;
    private static final long PROBE;
    private static final long SECONDARY;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class> tk = Thread.class;
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

//Thread :
 // The following three initially uninitialized fields are exclusively
    // managed by class java.util.concurrent.ThreadLocalRandom. These
    // fields are used to build the high-performance PRNGs in the
    // concurrent code, and we can not risk accidental false sharing.
    // Hence, the fields are isolated with @Contended.

    /** The current seed for a ThreadLocalRandom */
    @sun.misc.Contended("tlr")
    long threadLocalRandomSeed;

    /** Probe hash value; nonzero if threadLocalRandomSeed initialized */
    @sun.misc.Contended("tlr")
    int threadLocalRandomProbe;

    /** Secondary seed isolated from public ThreadLocalRandom sequence */
    @sun.misc.Contended("tlr")
    int threadLocalRandomSecondarySeed;


// ThreadLocalRandom , , seed 。 , 。
    final long nextSeed() {
        Thread t; long r; // read and update per-thread seed
        UNSAFE.putLong(t = Thread.currentThread(), SEED,
                       r = UNSAFE.getLong(t, SEED) + GAMMA);
        return r;
    }

ThreadLocalRandomでは、スレッドごとに、そのスレッドの変数seedが読み出され、更新される.複数のスレッドが共有される問題はないため、同時パフォーマンスに影響を与える問題もありません.