ConcurrentHashMap 1.8のスレッドプローブハッシュについて
13509 ワード
ConcurrentHashMapは,キー値対個数を加算したaddCount関数において,ThreadLocalRandom.getProbe()を用いてスレッドのプローブハッシュ値を得た.
ここで,このプローブハッシュ値の役割はハッシュスレッドであり,スレッドと配列中の不要要素を対応させ,スレッドが同じ配列要素を競合することをできるだけ避ける.プローブハッシュ値とmapで使用されるハッシュ値の違いは、スレッドに配列要素競合が発生すると、スレッドのプローブハッシュ値を変更して、スレッドに別の配列要素を使用させることができますが、mapのkeyオブジェクトのハッシュ値は、valueを位置決めする必要があるため、必ず変更できません.
では、このプローブハッシュ値はどこで計算されますか?この問題を持って私たちは下を見続けた.ThreadLocalRandom.getProbe()メソッドは次のとおりです.
PROBEって何?
PROBEは、ThreadクラスthreadLocalRandomProbeフィールドのオフセット量を表していることがわかります.したがってgetProbeメソッドの機能は、現在のスレッドthreadLocalRandomProbeフィールドの値を簡単に返すことです.
次にThreadクラスに行ってこのthreadLocalRandomProbeフィールドを見てみましょう.
Threadクラスはこのフィールドを定義しただけで、初期化されていません.その初期化作業はThreadLocalRandomクラスによって行われます.ThreadLocalRandomクラスのlocalInitメソッドは初期化作業を完了し、
SEEDはPROBEと同様であり、ThreadクラスthreadLocalRandomSeedフィールドのオフセット量を表す.
ThreadLocalRandomクラスのこのlocalInitメソッドでは、現在のスレッドのthreadLocalRandomSeedフィールドとthreadLocalRandomProbeフィールドを同時に初期化します.
したがって、ThreadクラスthreadLocalRandomProbeフィールドのコメントでは、nonzero if threadLocalRandomSeed initializedと言います.つまり、threadLocalRandomSeedフィールドが初期化されると、threadLocalRandomProbeフィールドはゼロではありません.2つは同時に初期化されているからです
また、現在のスレッドthreadLocalRandomProbeの値は、ThreadLocalRandomProbeクラスのadvanceProbeメソッドで変更することもできます.
ConcurrentHashMapのfullAddCountメソッドは、ThreadLocalRandom.localInit()を呼び出して現在のスレッドのプローブハッシュ値を初期化します.スレッド競合が発生すると、ThreadLocalRandom.advanceProbe(h)を呼び出して現在のスレッドのプローブハッシュ値を変更します.
ここで,このプローブハッシュ値の役割はハッシュスレッドであり,スレッドと配列中の不要要素を対応させ,スレッドが同じ配列要素を競合することをできるだけ避ける.プローブハッシュ値と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);
...
}