记录,分享
分类: Java
2017-03-16 12:10:04
[ ConcurrentMap ]
|- ConcurrentHashMap
|- ConcurrentSkipListMap
一、ConcurrentMap 主要方法
只有以下四个方法,能够保证是原子操作的。 ConcurrentHashMap和 ConcurrentSkipListMap的其他方法无法保证原子性。
1. V putIfAbsent(K key,V value) :
如果不存在key对应的值,则将value以key加入Map,并返回null,否则返回key对应的旧值。相当于
if (!map.containsKey(key))
return map.put(key, value);
else
return map.get(key);
如果该接口实现不允许添加null值(jdk提供的两个类都是如此),可以通过断返回值是否为null验证是否添加成功。
V ret = putIfAbsent(key, value);
if(ret == null){
//sucess
}else{
//fail to add, key exists
}
2. boolean remove(Object key, Object value) :
仅当key-value键值对存在时,才移除该键值。相当于
if (map.containsKey(key) && map.get(key).equals(value)) {
map.remove(key);
return true;
} else return false;
3. V replace(K key, V value) :
仅当key存在时,才将value添加进map,否则返回null。与putIfAbsent()相反。相当于
if (map.containsKey(key)) {
return map.put(key, value);
} else return null;
4. boolean replace(K key, V oldValue, V newValue) :
仅当key存在并且对应值为oldValue时,才将newValue添加进map,并返回true,否则返回false。相当于
if (map.containsKey(key) && map.get(key).equals(oldValue)) {
map.put(key, newValue);
return true;
} else return false;
二、ConcurrentHashMap实现线程安全的原理
一个ConcurrentHashMap 由多个segment 组成,每个segment 包含一个Entity 的数组。通过hash() 函数得到key 的哈希值,在得到相应的segment,在通过segment 存储Entity。这里比HashMap 多了一个segment 类。该类继承了ReentrantLock 类,所以本身是一个锁。当多线程对ConcurrentHashMap 操作时,不是完全锁住map, 而是锁住相应的segment 。这样提高了并发效率。
缺点:当遍历map 中的元素时,需要获取所有的segment 的锁,使用遍历时慢。锁的增多,占用了系统的资源。使得对整个集合进行操作的一些方法(例如 size() 或 isEmpty() )的实现更加困难,因为这些方法要求一次获得许多的锁,并且还存在返回不正确的结果的风险。
一些细节:
1、public V get(Object key)不涉及到锁,也就是说获得对象时没有使用锁;
2、keySet().iterator()及keys(),获取的Iterator、Enumeration变量是单线程访问安全的,多线程访问时要么生成多个Iterator、Enumeration(通过调用相应的获取方法),要么以ConcurrentHashMap变量为锁进行同步(synchronized该变量);ConcurrentHashMap变量是多线程访问安全的,尽管是多线程访问,多数情况下应该没有锁争用;
3、put、remove方法要使用锁,但并不一定有锁争用,原因在于ConcurrentHashMap将缓存的变量分到多个Segment,每个Segment上有一个锁,只要多个线程访问的不是一个Segment就没有锁争用,就没有堵塞,各线程用各自的锁,ConcurrentHashMap缺省情况下生成16个Segment,也就是允许16个线程并发的更新而尽量没有锁争用;
4、Iterator、Enumeration获得的对象,不一定是和其它更新线程同步,获得的对象可能是更新前的对象。ConcurrentHashMap允许一边更新、一边遍历,因为他的遍历器的next()方法, 每次都是返回一个new的WriteThroughEntry, 这个东西保证了你在获取到Entry以后即使Map遭到修改, 也不会影响你当前遍历的结果。但是, 如果你对WriteThroughEntry进行setValue操作, 还是可以影响到原来的map的。
5、有些情况下这种不一致是允许的,如果需要最大的性能、吞吐量,则正好使用ConcurrentHashMap。
三、ConcurrentSkipListMap 实现线程安全的原理
所有的修改操作都是使用CAS(UNSAFE.compareAndSwapObject()),只要失败就会重试,直至成功,所以就算多线程并发操作也不会出现错误,而且通过CAS避免了使用锁,性能比用锁好很多。