Java ConcurrentHashMap クラスは、Concurrency Collection Classes の一部です。これはハッシュテーブルの実装であり、同時にリトリーバルと更新をサポートします。これは、ConcurrentModificationException を回避するためにマルチスレッド環境で使用されます。
ConcurrentHashMap
コレクションを反復処理しながら変更しようとすると、ConcurrentModificationException
が発生します。Java 1.5 では、このシナリオを克服するために java.util.concurrent
パッケージで Concurrent クラスが導入されました。ConcurrentHashMap は反復処理中に Map を変更することを許可する Map 実装です。ConcurrentHashMap の操作はスレッドセーフです。ConcurrentHashMap はキーと値の両方に null を許可しません。
Java ConcurrentHashMap の例
ConcurrentHashMap
クラスは、HashMap と類似していますが、スレッドセーフで反復処理中の変更を許可します。
package com.journaldev.util;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ConcurrentHashMapExample {
public static void main(String[] args) {
//ConcurrentHashMap
Map<String,String> myMap = new ConcurrentHashMap<String,String>();
myMap.put("1", "1");
myMap.put("2", "1");
myMap.put("3", "1");
myMap.put("4", "1");
myMap.put("5", "1");
myMap.put("6", "1");
System.out.println("ConcurrentHashMap before iterator: "+myMap);
Iterator<String> it = myMap.keySet().iterator();
while(it.hasNext()){
String key = it.next();
if(key.equals("3")) myMap.put(key+"new", "new3");
}
System.out.println("ConcurrentHashMap after iterator: "+myMap);
//HashMap
myMap = new HashMap<String,String>();
myMap.put("1", "1");
myMap.put("2", "1");
myMap.put("3", "1");
myMap.put("4", "1");
myMap.put("5", "1");
myMap.put("6", "1");
System.out.println("HashMap before iterator: "+myMap);
Iterator<String> it1 = myMap.keySet().iterator();
while(it1.hasNext()){
String key = it1.next();
if(key.equals("3")) myMap.put(key+"new", "new3");
}
System.out.println("HashMap after iterator: "+myMap);
}
}
出力:
ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(HashMap.java:793)
at java.util.HashMap$KeyIterator.next(HashMap.java:828)
at com.test.ConcurrentHashMapExample.main(ConcurrentHashMapExample.java:44)
ConcurrentHashMapは、イテレーション中に新しいエントリをマップに追加する際に注意を払いますが、HashMapはConcurrentModificationException
をスローします。例外のスタックトレースを注意深く見てみましょう。次の文が例外をスローしたことを意味します。
String key = it1.next();
これは、新しいエントリがHashMapに挿入されたが、イテレータが失敗したことを意味します。実際、コレクションオブジェクト上のイテレータはfail-fastです。つまり、コレクションオブジェクトの構造またはエントリ数の変更が例外をトリガーします。
イテレータはコレクションの変更をどのように知るのですか?
私たちはHashMapからキーのセットを取得し、それを繰り返しています。HashMapには変更の数をカウントする変数が含まれており、イテレータはそのnext()関数を呼び出すときにそれを使用します。HashMap.java
:
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient volatile int modCount;
新しいエントリを挿入するときにイテレータループから抜けるようにコードを少し変更しましょう。行う必要があるのは、put呼び出しの後にbreak文を追加するだけです。
if(key.equals("3")){
myMap.put(key+"new", "new3");
break;
}
上記のコードでの出力:
ConcurrentHashMap before iterator: {1=1, 5=1, 6=1, 3=1, 4=1, 2=1}
ConcurrentHashMap after iterator: {1=1, 3new=new3, 5=1, 6=1, 3=1, 4=1, 2=1}
HashMap before iterator: {3=1, 2=1, 1=1, 6=1, 5=1, 4=1}
HashMap after iterator: {3=1, 2=1, 1=1, 3new=new3, 6=1, 5=1, 4=1}
キーの値が変更された場合、何が起こるのでしょうか?
新しいエントリを追加せずに既存のキー値ペアを更新した場合はどうなりますか? 例外が発生しますか?元のプログラムのコードを変更して、確認してみましょう。
//myMap.put(key+"new", "new3");
myMap.put(key, "new3");
コレクションが変更されましたが、その構造は同じままですので、例外は発生しません。
さらに読む
コレクションオブジェクトとIteratorを作成する際に、その角かっこを注意しましたか?これはジェネリクスと呼ばれ、コンパイル時の型チェックであり、実行時のClassCastExceptionを排除する際に非常に強力です。 Java Generics Exampleでジェネリクスについて詳しく学んでください。また、Java Collections Interview QuestionsとIterator Design Pattern in Javaも読んでみてください。
GitHubリポジトリからさらにJavaコレクションの例をチェックできます。
参考文献:APIドキュメント
Source:
https://www.digitalocean.com/community/tutorials/concurrenthashmap-in-java