ConcurrentHashMap Code Reading

85
ConcurrentHashMap Code Reading 角田 直行 <[email protected]>

Transcript of ConcurrentHashMap Code Reading

Page 1: ConcurrentHashMap Code Reading

ConcurrentHashMapCode Reading

角田 直行 <[email protected]>

Page 2: ConcurrentHashMap Code Reading

Agenda

•準備•Overview•読む•まとめ

Page 3: ConcurrentHashMap Code Reading

準備

Page 4: ConcurrentHashMap Code Reading

準備するもの

• Eclipse• ソース• ConcurrentHashMap.java• テストケース• JSR166TestCase.java• ConcurrentHashMapTest.java

• ConcurrentHashMapに関する参照資料• 気合い

Page 5: ConcurrentHashMap Code Reading

ソースの取得場所

•ConcurrentHashMap.javaはJDKより•テストケースはDoug Leaさんのサイトよりhttp://gee.cs.oswego.edu/dl/concurrency-interest/

Page 6: ConcurrentHashMap Code Reading

参照した資料

• JavaDochttp://java.sun.com/javase/ja/6/docs/ja/api/java/util/concurrent/ConcurrentHashMap.html

• IBM developerWorks:優れたHashMapの構築http://www.ibm.com/developerworks/jp/java/library/j-jtp08223/

• ITアーキテクト J2SE 5.0の新機能http://www.itarchitect.jp/technology_and_programming/-/24161-3.html

• Servlet Gardern@はてなhttp://d.hatena.ne.jp/yokolet/20071005

Page 7: ConcurrentHashMap Code Reading

Overview

Page 8: ConcurrentHashMap Code Reading

Overview

• Constants: 6つ• Field: 6つ• ユーティリティ: 2つ(hash(), segmentFor())• インナークラス: 2つ(HashEntry, Segment)• Public メソッド: 24コ(Constructorは5つ)• Iteratorサポート: 8つ• Serializationサポート: 2つ(writeObject(), readObject())

Page 9: ConcurrentHashMap Code Reading

多分重要なトコ

• Constants: 6つ• Field: 6つ• ユーティリティ: 2つ(hash(), segmentFor())• インナークラス: 2つ(HashEntry, Segment)• Public メソッド: 24コ(Constructorは5つ)• Iteratorサポート: 8つ• Serializationサポート: 2つ(writeObject(), readObject())

Page 10: ConcurrentHashMap Code Reading

今回は割愛するトコ

• Constants: 6つ• Field: 6つ• ユーティリティ: 2つ(hash(), segmentFor())• インナークラス: 2つ(HashEntry, Segment)• Public メソッド: 24コ(Constructorは5つ)• Iteratorサポート: 8つ• Serializationサポート: 2つ(writeObject(), readObject())

Page 11: ConcurrentHashMap Code Reading

読む

Page 12: ConcurrentHashMap Code Reading

public ConcurrentHashMap() { this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);}

public ConcurrentHashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);}

public ConcurrentHashMap(int initialCapacity, float loadFactor) { this(initialCapacity, loadFactor, DEFAULT_CONCURRENCY_LEVEL);}

コンストラクタ

Page 13: ConcurrentHashMap Code Reading

/** デフォルトの初期容量 */

static final int DEFAULT_INITIAL_CAPACITY = 16;/** デフォルトの負荷係数 */

static final float DEFAULT_LOAD_FACTOR = 0.75f;/** デフォルトの並行処理レベル */

static final int DEFAULT_CONCURRENCY_LEVEL = 16;

コンストラクタ

Page 14: ConcurrentHashMap Code Reading

/** 最大Segment数 1を16ビット分左にシフトしてるので65536 */

static final int MAX_SEGMENTS = 1 << 16;. . .public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel) { if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) throw new IllegalArgumentException();

// 並行処理レベルが最大セグメント数を超えてはならない if (concurrencyLevel > MAX_SEGMENTS) concurrencyLevel = MAX_SEGMENTS;

. . .

コンストラクタ

Page 15: ConcurrentHashMap Code Reading

// SegmentとHashエントリの最適な引数を見つける int sshift = 0; int ssize = 1; while (ssize < concurrencyLevel) { ++sshift; ssize <<= 1; } // currencyLevelが16(デフォルト)の時、sshiftは4、ssizeは16になる segmentShift = 32 - sshift; // 28(32は32bitのこと?)

segmentMask = ssize - 1; // 15(0b0111) this.segments = Segment.newArray(ssize);

Segment数の決定/** どのSegmentにあるかを特定するためのマスク値 */

final int segmentMask;/** どのSegmentにあるかを特定するためのシフト値 */

final int segmentShift;

Page 16: ConcurrentHashMap Code Reading

// Segmentの初期容量の算出 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; int c = initialCapacity / ssize; if (c * ssize < initialCapacity) ++c; int cap = 1; while (cap < c) cap <<= 1;

for (int i = 0; i < this.segments.length; ++i) this.segments[i] = new Segment<K,V>(cap, loadFactor);}

Segmentの生成/** 最大容量 1を30ビット分左にシフトしてるので1073741824 */

static final int MAXIMUM_CAPACITY = 1 << 30;

Page 17: ConcurrentHashMap Code Reading

Segmentとは

•ConcurrentHashMap.javaにて定義されているインナークラス

• 1つのConcurrentHashMapインスタンスに複数(デフォルト16)のSegmentを持つ

•ハッシュテーブルの特別バージョン• ReentrantLockのサブクラス

Page 18: ConcurrentHashMap Code Reading

static final class Segment<K,V> extends ReentrantLock implements Serializable {. . .transient int threshold;// 各SegmentにHashEntryの配列を保持transient volatile HashEntry<K,V>[] table;. . .Segment(int initialCapacity, float lf) { loadFactor = lf; setTable(HashEntry.<K,V>newArray(initialCapacity));}. . .void setTable(HashEntry<K,V>[] newTable) { threshold = (int)(newTable.length * loadFactor); table = newTable;}

Segmentのコンストラクタ

Page 19: ConcurrentHashMap Code Reading

HashEntryとは

•ConcurrentHashMap.javaにて定義されているインナークラス

• 1つのSegmentインスタンスに1つ以上のHashEntryを持つ

•フィールドにkey, hash値, value, next(次のHashEntry)を持つ

• key, hash, nextはfinal

Page 20: ConcurrentHashMap Code Reading

static final class HashEntry<K,V> { final K key; final int hash; volatile V value; final HashEntry<K,V> next; HashEntry(K key, int hash, HashEntry<K,V> next, V value) { this.key = key; this.hash = hash; this.next = next; this.value = value; } @SuppressWarnings("unchecked") static final <K,V> HashEntry<K,V>[] newArray(int i) { return new HashEntry[i]; }}

HashEntry

Page 21: ConcurrentHashMap Code Reading

Segment、HashEntryの構造

Page 22: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment、HashEntryの構造

Page 23: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

・・・

Segment、HashEntryの構造

Page 24: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

HashEntry

・・・

Segment、HashEntryの構造

Page 25: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

HashEntry

・・・

HashEntry

Segment、HashEntryの構造

Page 26: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

HashEntry

・・

HashEntry

HashEntry

Segment、HashEntryの構造

Page 27: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

HashEntry

・・

HashEntry

HashEntry

HashEntry

Segment、HashEntryの構造

Page 28: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

HashEntry

・・

HashEntry

HashEntry

HashEntry

HashEntry

Segment、HashEntryの構造

Page 29: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

HashEntry

・・

HashEntry

HashEntry

HashEntry

HashEntry

Segment、HashEntryの構造

実際のチェーン数は1~2個くらい

Page 30: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

HashEntry

・・

HashEntry

HashEntry

HashEntry

HashEntry

HashEntry

Segment、HashEntryの構造

実際のチェーン数は1~2個くらい

Page 31: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

HashEntry

・・

HashEntry

HashEntry

HashEntry

HashEntry

HashEntry

Segment、HashEntryの構造

実際のチェーン数は1~2個くらい

全Segmentが埋まったらSegmentのサイズは2倍に拡張される

Page 32: ConcurrentHashMap Code Reading

public V get(Object key) {// 与えられたハッシュ値を元にちゃんとしたハッシュ値を求める

int hash = hash(key.hashCode());// ハッシュ値に該当するセグメントを見つけSegment#getをcall

return segmentFor(hash).get(key, hash);}

ConcurrentHashMap#get

Page 33: ConcurrentHashMap Code Reading

public boolean containsKey(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).containsKey(key, hash);}public V put(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value, false);}public V putIfAbsent(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value, true);}

他の主要メソッドも似たような処理

public boolean remove(Object key, Object value) { int hash = hash(key.hashCode()); if (value == null) return false; return segmentFor(hash).remove(key, hash, value) != null;}public boolean replace(K key, V oldValue, V newValue) { if (oldValue == null || newValue == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).replace(key, hash, oldValue, newValue);}public V replace(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).replace(key, hash, value);}

Page 34: ConcurrentHashMap Code Reading

public boolean containsKey(Object key) { int hash = hash(key.hashCode()); return segmentFor(hash).containsKey(key, hash);}public V put(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value, false);}public V putIfAbsent(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).put(key, hash, value, true);}

他の主要メソッドも似たような処理

public boolean remove(Object key, Object value) { int hash = hash(key.hashCode()); if (value == null) return false; return segmentFor(hash).remove(key, hash, value) != null;}public boolean replace(K key, V oldValue, V newValue) { if (oldValue == null || newValue == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).replace(key, hash, oldValue, newValue);}public V replace(K key, V value) { if (value == null) throw new NullPointerException(); int hash = hash(key.hashCode()); return segmentFor(hash).replace(key, hash, value);}

Page 35: ConcurrentHashMap Code Reading

private static int hash(int h) { // Spread bits to regularize both segment and index locations, // using variant of single-word Wang/Jenkins hash. h += (h << 15) ^ 0xffffcd7d; h ^= (h >>> 10); h += (h << 3); h ^= (h >>> 6); h += (h << 2) + (h << 14); return h ^ (h >>> 16);}

ConcurrentHashMap#hash

何やっているかわかりません・・・orz

http://www.concentric.net/~Ttwang/tech/inthash.htm が参考ソース?

HashMap#hashよりBetterな実装に置き換えてる

Page 36: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

・・・

いいハッシュ値を返すと・・・

Page 37: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

・・・

いいハッシュ値を返すと・・・

PUT

(“1”, “a”)

Page 38: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

いいハッシュ値を返すと・・・

Page 39: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

いいハッシュ値を返すと・・・

(“2”, “b”)

PUT

Page 40: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

(“2”, “b”)

いいハッシュ値を返すと・・・

Page 41: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

(“2”, “b”)

いいハッシュ値を返すと・・・

(“3”, “c”)

PUT

Page 42: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

(“2”, “b”)

(“3”, “c”)

いいハッシュ値を返すと・・・

Page 43: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

(“2”, “b”)

(“3”, “c”)

いいハッシュ値を返すと・・・

ちゃんとバラけてくれる!

Page 44: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

・・・

悪いハッシュ値を返すと・・・

Page 45: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

・・・

悪いハッシュ値を返すと・・・

PUT

(“1”, “a”)

Page 46: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

悪いハッシュ値を返すと・・・

Page 47: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

悪いハッシュ値を返すと・・・

(“2”, “b”)

PUT

Page 48: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

(“2”, “b”)

悪いハッシュ値を返すと・・・

Page 49: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

(“2”, “b”)

悪いハッシュ値を返すと・・・

(“3”, “c”)

PUT

Page 50: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

(“2”, “b”) (“3”, “c”)

悪いハッシュ値を返すと・・・

Page 51: ConcurrentHashMap Code Reading

ConcurrentHashMap

Segment

Segment

Segment

(“1”, “a”)

・・・

(“2”, “b”) (“3”, “c”)

悪いハッシュ値を返すと・・・

偏ってしまい、すごく効率が悪くなる!

Page 52: ConcurrentHashMap Code Reading

V get(Object key, int hash) { if (count != 0) { // read-volatile

// hashに相当するHashEntryの要素を取り出す HashEntry<K,V> e = getFirst(hash); while (e != null) { if (e.hash == hash && key.equals(e.key)) {

// HashEntryのハッシュ値とキーが両方とも合えば V v = e.value; if (v != null) return v; return readValueUnderLock(e); // recheck } e = e.next; } } return null;}

Segment#get

Page 53: ConcurrentHashMap Code Reading

boolean containsKey(Object key, int hash) { if (count != 0) { // read-volatile HashEntry<K,V> e = getFirst(hash); while (e != null) { if (e.hash == hash && key.equals(e.key)) return true; e = e.next; } } return false;}

Segment#containsKey基本Segment#getと同じ

Page 54: ConcurrentHashMap Code Reading

boolean containsValue(Object value) { if (count != 0) { // read-volatile HashEntry<K,V>[] tab = table; // 変化する恐れがあるので作業用に置いてる? int len = tab.length; for (int i = 0 ; i < len; i++) { for (HashEntry<K,V> e = tab[i]; e != null; e = e.next) { V v = e.value; if (v == null) // recheck v = readValueUnderLock(e); // 理屈上こういうことがありうるらしい if (value.equals(v)) return true; } } } return false;}

Segment#containsValue

Page 55: ConcurrentHashMap Code Reading

V put(K key, int hash, V value, boolean onlyIfAbsent) { lock(); // HashEntryの更新系(put, remove, replace...)はロックが必要 try { int c = count; if (c++ > threshold) // ensure capacity rehash(); // サイズがしきい値を超えたら各要素のハッシュ値を再計算 HashEntry<K,V>[] tab = table; int index = hash & (tab.length - 1); HashEntry<K,V> first = tab[index]; HashEntry<K,V> e = first; while (e != null && (e.hash != hash || !key.equals(e.key))) // どういうケース?

e = e.next;. . .

Segment#put

Page 56: ConcurrentHashMap Code Reading

V oldValue; if (e != null) { oldValue = e.value; if (! onlyIfAbsent) e.value = value; } else { // 基本はここを通る oldValue = null; ++modCount; // HashEntryの構造が変更された回数を増やす tab[index] = new HashEntry<K,V>(key, hash, first, value); count = c; // write-volatile } return oldValue; } finally { unlock(); }}

Segment#put

Page 57: ConcurrentHashMap Code Reading

V remove(Object key, int hash, Object value) { lock(); try { int c = count - 1; // サイズを1つ減らす HashEntry<K,V>[] tab = table; int index = hash & (tab.length - 1); HashEntry<K,V> first = tab[index]; HashEntry<K,V> e = first; while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next; V oldValue = null;. . .

Segment#remove前半部分はSegment#putと大体同じ

Page 58: ConcurrentHashMap Code Reading

if (e != null) { V v = e.value; if (value == null || value.equals(v)) { oldValue = v; ++modCount; HashEntry<K,V> newFirst = e.next; for (HashEntry<K,V> p = first; p != e; p = p.next) newFirst = new HashEntry<K,V>(p.key, p.hash, newFirst, p.value); tab[index] = newFirst; count = c; // write-volatile } } return oldValue; } finally { unlock(); }}

Segment#remove

Page 59: ConcurrentHashMap Code Reading

if (e != null) { V v = e.value; if (value == null || value.equals(v)) { oldValue = v; ++modCount; HashEntry<K,V> newFirst = e.next; for (HashEntry<K,V> p = first; p != e; p = p.next) newFirst = new HashEntry<K,V>(p.key, p.hash, newFirst, p.value); tab[index] = newFirst; count = c; // write-volatile } } return oldValue; } finally { unlock(); }}

Segment#remove

Page 60: ConcurrentHashMap Code Reading

Segment#remove

• HashEntryにはnextという次の要素を表すフィールドがある

• removeが行われるとnextの付け替えが発生する

• nextフィールドはfinalなので削除した要素より前を作り直さなければならない

Page 61: ConcurrentHashMap Code Reading

SegmentA

Next = B

BNext = C

CNext = D

DNext = E

ENext = null

Page 62: ConcurrentHashMap Code Reading

SegmentA

Next = B

BNext = C

CNext = D

DNext = E

ENext = null

remove(“C”)

Page 63: ConcurrentHashMap Code Reading

SegmentA

Next = B

BNext = C

DNext = E

ENext = null

Page 64: ConcurrentHashMap Code Reading

SegmentA

Next = B

BNext = C

DNext = E

ENext = null

NextがfinalなのでDに変更できない!

Page 65: ConcurrentHashMap Code Reading

SegmentA

Next = B

BNext = C

DNext = E

ENext = null

NextがfinalなのでDに変更できない!

AとBは作り直し

Page 66: ConcurrentHashMap Code Reading

void rehash() { HashEntry<K,V>[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity >= MAXIMUM_CAPACITY) return;

/* * Reclassify nodes in each list to new Map. Because we are * using power-of-two expansion, the elements from each bin * must either stay at same index, or move with a power of two * offset. We eliminate unnecessary node creation by catching. . .

Segment#rehashput時にサイズがcapacityを超えた時に発生

Page 67: ConcurrentHashMap Code Reading

コメントの超意訳

• 各リストのノードを新しいMapに再分類する。2つの強力な拡張(SegmentとHashEntry)を使っているため、同じindexを保つか2つのoffsetを同時に移動させなければならない。nextフィールドは変わらないので、古いノードは再利用でき不必要なノードを作らなくてすむ。統計的に、デフォルトのthresholdだとテーブルを2倍にする時にクローンする必要があるのは約1/6。置き換わる古いノードはreaderスレッドがすぐにテーブルを走査することで参照されなくなりGC対象となる。

Page 68: ConcurrentHashMap Code Reading

void clear() { if (count != 0) { lock(); try { HashEntry<K,V>[] tab = table; for (int i = 0; i < tab.length ; i++) tab[i] = null; ++modCount; count = 0; // write-volatile } finally { unlock(); } }}

Segment#clear

Page 69: ConcurrentHashMap Code Reading

V replace(K key, int hash, V newValue) { lock(); try { HashEntry<K,V> e = getFirst(hash); while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next;

V oldValue = null; if (e != null) { oldValue = e.value; e.value = newValue; } return oldValue; } finally { unlock(); }}

Segment#replace

Page 70: ConcurrentHashMap Code Reading

boolean replace(K key, int hash, V oldValue, V newValue) { lock(); try { HashEntry<K,V> e = getFirst(hash); while (e != null && (e.hash != hash || !key.equals(e.key))) e = e.next;

boolean replaced = false; if (e != null && oldValue.equals(e.value)) { replaced = true; e.value = newValue; } return replaced; } finally { unlock(); }}

Segment#replace その2

Page 71: ConcurrentHashMap Code Reading

public boolean isEmpty() { final Segment<K,V>[] segments = this.segments; int[] mc = new int[segments.length]; int mcsum = 0; for (int i = 0; i < segments.length; ++i) { if (segments[i].count != 0) return false; else // 全segmentのサイズが0の時、それぞれの変更回数の和を計算する mcsum += mc[i] = segments[i].modCount; }. . .

ConcurrentHashMap#isEmpty

Page 72: ConcurrentHashMap Code Reading

if (mcsum != 0) { // 何かしらの変更 (ABA問題) があった場合 for (int i = 0; i < segments.length; ++i) { if (segments[i].count != 0 || mc[i] != segments[i].modCount) return false; } } return true;}

ConcurrentHashMap#isEmpty

Page 73: ConcurrentHashMap Code Reading

isEmptyのコメント超意訳

• いつの時点でもテーブルが空にならなかった場合、あるsegmentの一要素が追加され別で走査中に削除されるというABA問題(「Java並行処理プログラミング」15章参照)を避けるために各segmentのmodCountを追跡する。他にABA問題の影響を受ける可能性があるsize()やcontainsValue()メソッドでも同様にmodCountを使っている。

Page 74: ConcurrentHashMap Code Reading

ConcurrentHashMap#size

•まずはロック無しで2回までトライする•数えている間に、別で変更処理が行われたらやり直す

• 2回とも邪魔されてしまったら全Segmentにロックをかけて数える

Page 75: ConcurrentHashMap Code Reading

public int size() { final Segment<K,V>[] segments = this.segments; long sum = 0; long check = 0; int[] mc = new int[segments.length]; // Try a few times to get accurate count. On failure due to // continuous async changes in table, resort to locking. for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { check = 0; sum = 0; int mcsum = 0;. . .

ConcurrentHashMap#size/** size()とcontainsValue()で使われる */

static final int RETRIES_BEFORE_LOCK = 2;

Page 76: ConcurrentHashMap Code Reading

for (int i = 0; i < segments.length; ++i) { sum += segments[i].count; mcsum += mc[i] = segments[i].modCount; } if (mcsum != 0) { // 初期状態から変更が行われた for (int i = 0; i < segments.length; ++i) { check += segments[i].count; if (mc[i] != segments[i].modCount) { // 変更された check = -1; // force retry(「Java並行処理プログラミング」P269)

break; } } } if (check == sum) break; }

ConcurrentHashMap#size

Page 77: ConcurrentHashMap Code Reading

if (check != sum) { // Resort to locking all segments sum = 0; for (int i = 0; i < segments.length; ++i) //全Segmentをロック segments[i].lock(); for (int i = 0; i < segments.length; ++i) sum += segments[i].count; for (int i = 0; i < segments.length; ++i) segments[i].unlock(); } if (sum > Integer.MAX_VALUE) return Integer.MAX_VALUE; else return (int)sum;}

ConcurrentHashMap#size

Page 78: ConcurrentHashMap Code Reading

public boolean containsValue(Object value) { if (value == null) throw new NullPointerException(); // See explanation of modCount use above final Segment<K,V>[] segments = this.segments; int[] mc = new int[segments.length];

// Try a few times without locking for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { int sum = 0; int mcsum = 0;. . .

ConcurrentHashMap#containsValue

ConcurrentHashMap#sizeと大体同じ

Page 79: ConcurrentHashMap Code Reading

for (int i = 0; i < segments.length; ++i) { int c = segments[i].count; // メモリを同期化 mcsum += mc[i] = segments[i].modCount; if (segments[i].containsValue(value)) return true; } boolean cleanSweep = true; if (mcsum != 0) { // 初期状態から変更された for (int i = 0; i < segments.length; ++i) { int c = segments[i].count; // メモリを同期化 if (mc[i] != segments[i].modCount) { // 変更されたのでやり直し cleanSweep = false; break; } } } if (cleanSweep) return false; }

ConcurrentHashMap#containsValue

Page 80: ConcurrentHashMap Code Reading

// Resort to locking all segments for (int i = 0; i < segments.length; ++i) segments[i].lock(); boolean found = false; try { for (int i = 0; i < segments.length; ++i) { if (segments[i].containsValue(value)) { // Segment#containsValue found = true; break; } } } finally { for (int i = 0; i < segments.length; ++i) segments[i].unlock(); } return found;}

ConcurrentHashMap#containsValue

Page 81: ConcurrentHashMap Code Reading

まとめ

Page 82: ConcurrentHashMap Code Reading

まとめ

•ConcurrentHashMapは、SegmentとHashEntryというデータ構造が肝

•高速化のために色んなことをやってる• finalでread時はロックしないように• hash値計算でちゃんとばらけさせる•ロック処理を最小限に抑える

Page 83: ConcurrentHashMap Code Reading

ちなみに

!"#$$$$%&''!$()*+$,-./01.2$3456$

77768)*+.-./01.6591

: & ! ; < = > ?'

<

:'

:<

&'

&<

!'

@AB08C.

DE9F.G.05

HIE""

JKDE""

HIE><

JKDE><

: & ! ; < = > ?'

<

:'

:<

&'

&<

!'

@AB08C.

DE9F.G.05

HI

JKD

:L$@8M+0 :D$@8M+0

(DN$&6;OK)$P$&$QA/R$5F*.

• ConcurrentHashMapより速いNonBlockingHashMapというのがある

•Azul SystemsのCliff Click氏作

Page 85: ConcurrentHashMap Code Reading

ご清聴ありがとうございました!