ConcurrentHashMap 1.8のスレッドプローブハッシュについて

13509 ワード

ConcurrentHashMapは,キー値対個数を加算したaddCount関数において,ThreadLocalRandom.getProbe()を用いてスレッドのプローブハッシュ値を得た.
ここで,このプローブハッシュ値の役割はハッシュスレッドであり,スレッドと配列中の不要要素を対応させ,スレッドが同じ配列要素を競合することをできるだけ避ける.プローブハッシュ値とmapで使用されるハッシュ値の違いは、スレッドに配列要素競合が発生すると、スレッドのプローブハッシュ値を変更して、スレッドに別の配列要素を使用させることができますが、mapのkeyオブジェクトのハッシュ値は、valueを位置決めする必要があるため、必ず変更できません.
では、このプローブハッシュ値はどこで計算されますか?この問題を持って私たちは下を見続けた.ThreadLocalRandom.getProbe()メソッドは次のとおりです.
    /**
     * Returns the probe value for the current thread without forcing
     * initialization. Note that invoking ThreadLocalRandom.current()
     * can be used to force initialization on zero return.
     */
    static final int getProbe() {
        return UNSAFE.getInt(Thread.currentThread(), PROBE);
    }

PROBEって何?
    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    ...
    private static final long PROBE;
    ...
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            ...
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            ...
        } catch (Exception e) {
            throw new Error(e);
        }
    }  

PROBEは、ThreadクラスthreadLocalRandomProbeフィールドのオフセット量を表していることがわかります.したがってgetProbeメソッドの機能は、現在のスレッドthreadLocalRandomProbeフィールドの値を簡単に返すことです.
次にThreadクラスに行ってこのthreadLocalRandomProbeフィールドを見てみましょう.
    /** Probe hash value; nonzero if threadLocalRandomSeed initialized */
    @sun.misc.Contended("tlr")
    int threadLocalRandomProbe;

Threadクラスはこのフィールドを定義しただけで、初期化されていません.その初期化作業はThreadLocalRandomクラスによって行われます.ThreadLocalRandomクラスのlocalInitメソッドは初期化作業を完了し、
    /**
     * 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() {
    	// probeGenerator     AtomicInteger   
    	// PROBE_INCREMENT        ,   0x9e3779b9
        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            threadLocalRandomSeed   
        UNSAFE.putLong(t, SEED, seed);
        //    Unsafe            threadLocalRandomProbe   
        UNSAFE.putInt(t, PROBE, probe);
    }

SEEDはPROBEと同様であり、ThreadクラスthreadLocalRandomSeedフィールドのオフセット量を表す.
ThreadLocalRandomクラスのこのlocalInitメソッドでは、現在のスレッドのthreadLocalRandomSeedフィールドとthreadLocalRandomProbeフィールドを同時に初期化します.
したがって、ThreadクラスthreadLocalRandomProbeフィールドのコメントでは、nonzero if threadLocalRandomSeed initializedと言います.つまり、threadLocalRandomSeedフィールドが初期化されると、threadLocalRandomProbeフィールドはゼロではありません.2つは同時に初期化されているからです
また、現在のスレッドthreadLocalRandomProbeの値は、ThreadLocalRandomProbeクラスのadvanceProbeメソッドで変更することもできます.
    /**
     * Pseudo-randomly advances and records the given probe value for the
     * given thread.
     */
    static final int advanceProbe(int probe) {
        probe ^= probe << 13;   // xorshift
        probe ^= probe >>> 17;
        probe ^= probe << 5;
        UNSAFE.putInt(Thread.currentThread(), PROBE, probe);
        return probe;
    }

ConcurrentHashMapのfullAddCountメソッドは、ThreadLocalRandom.localInit()を呼び出して現在のスレッドのプローブハッシュ値を初期化します.スレッド競合が発生すると、ThreadLocalRandom.advanceProbe(h)を呼び出して現在のスレッドのプローブハッシュ値を変更します.

    private final void fullAddCount(long x, boolean wasUncontended) {
        int h;
        if ((h = ThreadLocalRandom.getProbe()) == 0) {
            ThreadLocalRandom.localInit();      // force initialization
            h = ThreadLocalRandom.getProbe();
            wasUncontended = true;
        }
        ...
        h = ThreadLocalRandom.advanceProbe(h);
        ...
    }