现在的位置: 首页 > 综合 > 正文

Java ConcurrentHashMap类

2018年06月05日 ⁄ 综合 ⁄ 共 3868字 ⁄ 字号 评论关闭

ConcurrentHashMap是Java5中支持高并发、高吞吐量的线程安全HashMap实现。

ConcurrentHashMap允许多个修改操作并发进行,其关键字在于使用了锁分离技术。它使用了多个锁来控制对Hash表的不同部分进行修改。ConcurrentHashMap内部使用段(segment)来表示这些不同的部分,每个段其实就是一个小的 Hash table,他们有自己的锁。只要多个修改操作发生在不同的段上,他们就可以并发进行。

有些方法需要跨段,比如size()和containsValue(),他们可能需要锁定整个表而不仅仅是某个段,这需要按顺序锁定所有的段,操作完毕之后又按顺序释放所有的段,这里的顺序很重要,否则可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证,这可以确保不会出现死锁,因为获得锁的顺序是固定的。不变性是多线程编程占有很重要的地位。

段数组的定义如下:

    /**
     * The segments, each of which is a specialized hash table
     */
    final Segment<K,V>[] segments;

不变(Immutable)和易变(Volatile)

ConcurrentHashMap完全允许多个读操作并发进行,读操作并不需要加锁。但如果使用传统的技术,如HashMap中的实现,如果允许可以在hash链的中间添加或删除元素,则读操作将得到不一致的数据。ConcurrentHashMap实现技术是保证HashEntry几乎是不可变的。HashEntry代表每个Hash链中的一个节点,其结构如下:

    static final class HashEntry<K,V> {
        final K key;
        final int hash;
        volatile V value;
        final HashEntry<K,V> next;
    }

从代码中可以看出,除了value不是final的,其它值都是final的,这意味着不能从hash链的中间或尾部添加或删除结点,因为这需要修改next引用值,所有的节点的修改只能从头部开始。对于put操作,可以一律添加到hash链的头部。但是对于remove操作,可能需要从中间删除一个节点,这就需要将删除节点前面所有节点复制一遍,最后一个节点指向要删除节点的下一个节点,为了确保读操作能够看到最新的值,将value设置成为volatile即易变的。

测试案例:模拟1000个并发,每个测试1000次操作,循环测试10轮,分别测试put和get操作。

public class ConcurrentHashMapTest {

	// 定义1000个线程
	private static final int threads = 1000;
	// 定义一个常量
	public static final int NUMBER = 1000;

	public static void main(String[] args) throws InterruptedException {
		// 创建一个线程同步的HashMap
		Map<String, Integer> hashMapSync = Collections.synchronizedMap(new HashMap<String, Integer>());
		// 创建一个非线程同步的HashMap
		Map<String, Integer> hashMap = new HashMap<String, Integer>();
		// 创建一个线程同步的ConcurrentHashMap对象
		Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<String, Integer>();
		// 创建哈希表
		Map<String, Integer> hashTable = new Hashtable<String, Integer>();

		// 定义三个变量
		long totalA = 0;
		long totalB = 0;
		long totalC = 0;

		// 遍历往集合中存数据
		for (int i = 0; i <= 10; i++) {
			totalA += testPut(hashMapSync);
			totalB += testPut(concurrentHashMap);
			totalC += testPut(hashTable);
		}

		// 打印存数据的效率
		System.out.println("存值的时间对比:");
		System.out.println("Put time HashMapSync=" + totalA + "ms.");
		System.out.println("Put time ConcurrentHashMap=" + totalB + "ms.");
		System.out.println("Put time Hashtable=" + totalC + "ms.");

		// 把变量置为0,测试取值的时间
		System.out.println("取值的时间对比:");
		totalA = 0;
		totalB = 0;
		totalC = 0;
		
		for (int i = 0; i <= 10; i++) {
			totalA += testGet(hashMapSync);
			totalB += testGet(concurrentHashMap);
			totalC += testGet(hashTable);
		}
		System.out.println("Get time HashMapSync=" + totalA + "ms.");
		System.out.println("Get time ConcurrentHashMap=" + totalB + "ms.");
		System.out.println("Get time Hashtable=" + totalC + "ms.");

	}

	/**
	 * 测试存储
	 * 
	 * @param map
	 * @return
	 * @throws InterruptedException
	 */
	private static long testPut(Map<String, Integer> map)
			throws InterruptedException {
		// 获取程序开始的时间
		long start = System.currentTimeMillis();
		// 循环1000个线程
		for (int i = 0; i < threads; i++) {
			new MapPutThread(map).start();
		}
		while (MapPutThread.counter > 0) {
			Thread.sleep(1);
		}
		// 返回程序执行的时间
		return System.currentTimeMillis() - start;
	}

	/**
	 * 测试取值方法
	 * 
	 * @param map
	 * @return
	 * @throws InterruptedException
	 */
	private static long testGet(Map<String, Integer> map)
			throws InterruptedException {
		// 获取程序开始时间
		long start = System.currentTimeMillis();
		// 循环1000个程序
		for (int i = 0; i < threads; i++) {
			new MapGetThread(map).start();
		}
		while (MapGetThread.counter > 0) {
			Thread.sleep(1);
		}
		// 返回方法执行的时间
		return System.currentTimeMillis() - start;
	}

}

/**
 * 存值的线程
 * 
 * @author Liao
 */
class MapPutThread extends Thread {
	// 定义一个静态变量
	static int counter = 0;
	static Object lock = new Object();
	private Map<String, Integer> map;
	private String key = this.getId() + "";

	MapPutThread(Map<String, Integer> map) {
		synchronized (lock) {
			counter++;
		}
		this.map = map;
	}

	@Override
	public void run() {
		for (int i = 1; i <= ConcurrentHashMapTest.NUMBER; i++) {
			map.put(key, i);
		}

		synchronized (lock) {
			counter--;
		}
	}
}

/**
 * 取值的线程
 * 
 * @author Liao
 */
class MapGetThread extends Thread {
	static int counter = 0;
	static Object lock = new Object();
	private Map<String, Integer> map;
	private String key = this.getId() + "";

	public MapGetThread(Map<String, Integer> map) {
		synchronized (lock) {
			counter++;
		}
		this.map = map;
	}

	@Override
	public void run() {
		for (int i = 1; i <= ConcurrentHashMapTest.NUMBER; i++) {
			map.get(key);
		}
		synchronized (lock) {
			counter--;
		}
	}
}

测试结果:

从图中可以看出ConcurrentHashMap的效率是最高的。

抱歉!评论已关闭.