Collections.synchronizedMap()とConcurrentHashMapの違い


前述したCollections.synchronizedMap()とConcurrentHashMの両方がスレッド同期機能を提供しています.両者の違いはどこにあるのでしょうか.まずコードの例を見てみましょう.
    次のコードは、あるスレッドがmapを書き込み、別のスレッドがmapデータを読み出して印刷することを実現する.
package test.map;

import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;

public class MapTest2 {

	private static Map map1 = new HashMap();
	private static Map map2 = new Hashtable();
	private static Map map3 = new ConcurrentHashMap();
	private static Map map4 = Collections.synchronizedMap(new HashMap());

	private static Map map = map4;

	public static void main(String[] args) {
		new Thread(new Runnable() {

			@Override
			public void run() {
				while (true) {
					if (map.size() > 0) {
						for (Map.Entry entry : map.entrySet()) {
							System.out.println(String.format("%s: %s", entry.getKey(), entry.getValue()));
						}
						map.clear();
					}
					try {
						Thread.sleep((new Random().nextInt(10) + 1) * 1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();

		new Thread(new Runnable() {

			@Override
			public void run() {

				for (int i = 1; i <= 100; i++) {
					map.put("key" + i, "value" + i);
					try {
						Thread.sleep((new Random().nextInt(10) + 1) * 1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
		}).start();
	}
}

コードを実行すると、クラス内のオブジェクトmap値がmap 1,map 2,map 4の場合、プログラムはjava.util.C o n c u r r e ntModificationException異常を放出することがわかります.map=map 3の場合、コードは正常に動作します.
Collections.synchronizedMap()とConcurrentHashMapの主な違いは、Collections.synchronizedMap()とHashtableのように、実装上はmapのすべてのメソッドを呼び出すときにmap全体を同期させるが、ConcurrentHashMapの実装はより細かく、mapのすべてのバケツにロックをかけている.したがって、1つのスレッドがmapにアクセスする限り、他のスレッドはmapにアクセスできません.一方、1つのスレッドがConcurrentHashMapのバケツにアクセスしている場合、他のスレッドはmapに対していくつかの操作を実行できます.これにより、ConcurrentHashMapは、Collections.synchronizedMap()よりもパフォーマンスおよびセキュリティに優れています.同時に、同期操作はバケツに正確に制御されるので、mapを巡って他のスレッドがmapをデータ修正しようとしても、ConcurrentModificationExceptionは放出されません.    しかし、注意深い友人は、上記の例では、map=map 3の場合でも、最後に印刷された結果は100行ではないことを発見したかもしれません.なぜなら、Collections.synchronizedMap()とConcurrentHashMapのmap同期に対する原子操作の両方が機能するmapの方法では、mapは読み取りとクリアの間でスレッド間で同期しないからである.上記のコードの不足は、これらの同期mapを信頼しすぎて、混合操作による影響を無視していることです.正しい方法はmapの読み取りとクリアを原子操作と見なし,コードブロック全体にロックを加えることである.
    もう一つの違いは、ConcurrentHashMapはクラスの名前から、必然的にHashMapであることがわかります.一方、Collections.synchronizedMap()は、任意のMapインスタンスを受信し、Mapの同期を実現することができる.この例を見てください.
package test.map;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;

public class MapTest4 {

	private static void writeMap(Map map) {
		for (int i = 0; i < 10; i++) {
			map.put("key" + i, "value" + i);
		}
	}

	private static void printMap(Map map) {
		for (Map.Entry entry : map.entrySet()) {
			System.out.println("key: " + entry.getKey() + ", value: " + entry.getValue());
		}
	}

	public static void main(String[] args) {
		Map map1 = new HashMap();
		writeMap(map1);
		printMap(map1);

		System.out.println();

		Map map2 = Collections.synchronizedMap(new TreeMap());
		writeMap(map2);
		printMap(map2);

		System.out.println();

		Map map3 = new ConcurrentHashMap();
		writeMap(map3);
		printMap(map3);
	}
}
map 2はTreeMapであるため、最後に印刷された結果はKey値に従ってソートされ、map 3は明らかに結果の秩序を保証できない.