ConcurrentHashmap是jdk1.5之后引入的并发工具集合类,可以将其看作并发效率更高的map,用来替代Hashtable和synchronizedMap,但是ConcurrentHashmap只能保证自身数据在多线程的环境下不被破坏,而并不能保证业务逻辑的正确性。
举个例子:资源池中经常需要将某个资源放到一个map中缓存起来,等到需要的时候再从map中获取,代码如下:
code1:
private ConcurrentMap records = new ConcurrentHashMap();
private Record getOrCreate(String id) {
Record rec = records.get(id); //1
if (rec == null) {
// record does not yet exist
Record newRec = new Record(id);
rec = records.putIfAbsent(id, newRec);
if (rec == null) {
// put succeeded, use new value
rec = newRec;
}
}
return rec;
}
concurrenthashmap实现了concurrentmap接口:
public interface ConcurrentMap<K, V> extends Map<K, V> {
V putIfAbsent(K key, V value);
boolean remove(Object key, Object value);
boolean replace(K key, V oldValue, V newValue);
V replace(K key, V value);
}
其中特别介绍下putIfAbsent(K key,V value)方法:
将指定key与value关联起来,并且返回和指定key关联的老的value值,如果以前没有value和key关联,返回null
可以看到putIfAbsent方法的作用相当于以下代码:
if (!map.containsKey(key))
return map.put(key, value);
else
return map.get(key);
唯一区别就是putIfAbsent是原子操作
在code1这个例子中虽然使用了Concurrenthashmap,但是也有问题,问题出在1处,多个线程可能会创建多个newRec,可能会引发后续错误。
要避免这种资源的多次创建,可以使用double checked的方式实现,在资源池场景可以使用Double checked保证我们资源仅被创建一次 ,同时尽量降低同步的代价:
private ConcurrentMap records = new ConcurrentHashMap();
private Record getOrCreate(String id) {
Record rec = records.get(id);
if (rec == null) {
synchronized (this) {
rec = records.get(id);
if (rec == null) {
// record does not yet exist
Record newRec = new Record(id);
rec = records.putIfAbsent(id, newRec);
if (rec == null) {
// put succeeded, use new value
rec = newRec;
}
}
}
}
return rec;
}
Concurrenthashmap支持在运行时修改修改集合,而不抛出ConcurrentModificationException异常。举一个例子:
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 myMap = new ConcurrentHashMap();
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 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();
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 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可以支持向map中添加新元素,而HashMap则抛出了ConcurrentModificationException。事实上,集合对象的迭代器提供快速失败(Fail-Fast)的机制,即修改集合对象结构或者元素数量都会使迭代器触发这个异常。
Concurrenthashmap的简要总结
1. 在读取数据的时候不需要加锁,因为public V get(Object key)方法不涉及到锁
2. put、remove方法涉及到锁,但是也并一定有锁争用,因为Concurrenthashmap是分段加锁的,如果要获取的对象正好不在同一个段(segment)上,也就不会存在争夺锁的情况,Concurrenthashmap默认有16个段
3. ConcurrentHashMap允许一边更新、一边遍历,也就是说在Iterator对象遍历的时候,ConcurrentHashMap也可以进行remove,put操作,且遍历的数据会随着remove,put操作产出变化
版权声明:本文为博主原创文章,不过你想转载就转载。