ConcurrentHashMap的介绍
ConcurrentHashMap是一个线程安全,并且是一个高效的HashMap,ConcurrentMap,它是一个接口,是一个能够支持并发访问的java.util.map集合,在原有java.util.map接口基础上又新提供了4种方法,进一步扩展了原有Map的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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); }
|
(1) putIfAbsent: 与原有put方法不同的是,putIfAbsent方法中如果插入的key相同,则不替换原有的value值
(2) remove: 与原有remove方法不同的是,新remove方法中增加了对value的判断,如果要删除的key–value不能与Map中原有的key–value对应上,则不会删除该元素
(3) replace(K,V,V): 增加了对value值的判断,如果key–oldValue能与Map中原有的key–value对应上,才进行替换操作
(4) replace(K,V): 与上面的replace不同的是,此replace不会对Map中原有的key–value进行比较,如果key存在则直接替换
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| private static Logger log = Logger.getLogger(ConcurrentMapDemo.class); @Test public void test() { ConcurrentHashMap<Object, Object> concurrentHashMap = new ConcurrentHashMap<Object, Object>(); concurrentHashMap.put("name", "tom"); log.info("concurrentHashMap>>>>> " + concurrentHashMap.toString()); concurrentHashMap.putIfAbsent("name", "lucy"); log.info("concurrentHashMap>>>>> " + concurrentHashMap.toString()); concurrentHashMap.remove("name", "lucy"); log.info("concurrentHashMap>>>>> " + concurrentHashMap.toString()); concurrentHashMap.replace("name", "lucy", "newLucy"); log.info("concurrentHashMap>>>>> " + concurrentHashMap.toString()); concurrentHashMap.replace("name", "lucy"); log.info("concurrentHashMap>>>>> " + concurrentHashMap.toString()); }
|
其实,对于ConcurrentMap来说,我们更关注Map本身的操作,在并发情况下是如何实现数据安全的。在java.util.concurrent包中,ConcurrentMap的实现类主要以ConcurrentHashMap为主,接下来,我们具体来看下
从线程安全的角度来说,HashTable已经是一个线程安全的HashMap,说起ConcurrentHashMap,就不得不先提及下HashMap在线程不安全的表现
HashMap遍历删除
HashMap或者ArrayList边遍历边删除数据会报java.util.ConcurrentModificationException异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Test public void test() { Map<Long, String> mReqPacket = new HashMap<Long, String>(); for (long i = 0; i < 15; i++) { mReqPacket.put(i, i + ""); } for (Entry<Long, String> entry : mReqPacket.entrySet()) { long key = entry.getKey(); String value = entry.getValue(); if (key < 10) { mReqPacket.remove(key); } } for (Entry<Long, String> entry : mReqPacket.entrySet()) { log.info(entry.getKey() + " " + entry.getValue()); } }
|
运行结果:
1 2 3 4 5 6 7 8 9 10 11
| java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437) at java.util.HashMap$EntryIterator.next(HashMap.java:1471) at java.util.HashMap$EntryIterator.next(HashMap.java:1469) at map.ConcurrentMapDemo.test1(ConcurrentMapDemo.java:119) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:389) Process finished with exit code -1
|
使用迭代器删除元素,没有问题,不会出现异常 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Test public void test() { Map<Long, String> mReqPacket = new HashMap<Long, String>(); for (long i = 0; i < 15; i++) { mReqPacket.put(i, i + ""); } for (Iterator<Entry<Long, String>> iterator = mReqPacket.entrySet().iterator(); iterator.hasNext();) { Entry<Long, String> entry = iterator.next(); long key = entry.getKey(); if (key < 10) { iterator.remove(); } } for (Entry<Long, String> entry : mReqPacket.entrySet()) { log.info(entry.getKey() + " " + entry.getValue()); } }
|
ConcurrentHashMap允许一边更新、一边遍历,也就是说在Iterator对象遍历的时候,ConcurrentHashMap也可以进行remove,put操作,且遍历的数据会随着remove,put操作产出变化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| @Test public void test1() { Map<Long, String> conMap = new ConcurrentHashMap<Long, String>(); for (long i = 0; i < 15; i++) { conMap.put(i, i + ""); } for (Entry<Long, String> entry : conMap.entrySet()) { long key = entry.getKey(); if (key < 10) { conMap.remove(key); } } for (Entry<Long, String> entry : conMap.entrySet()) { log.info(entry.getKey() + " " + entry.getValue()); } }
|
ConcurrentHashMap多线程操作
对ConcurrentHashMap边遍历边删除或者增加操作不会产生异常(可以不用迭代方式删除元素),因为其内部已经做了维护,遍历的时候都能获得最新的值。即便是多个线程一起删除、添加元素也没问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| static Map<Long, String> conMap = new ConcurrentHashMap<Long, String>(); public static void main(String[] args) throws InterruptedException { for (long i = 0; i < 5; i++) { conMap.put(i, i + ""); } Thread thread = new Thread(new Runnable() { public void run() { for (Iterator<Entry<Long, String>> iterator = conMap.entrySet().iterator(); iterator.hasNext();) { Map.Entry<Long, String> entry = iterator.next(); log.info(entry.getKey() + " - " + entry.getValue()); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } }); Thread thread2 = new Thread(new Runnable() { public void run() { conMap.put(100l, "100"); log.info("ADD:" + 100); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } }); thread.start(); thread2.start(); Thread.sleep(3000); log.info("--------"); for (Map.Entry<Long, String> entry : conMap.entrySet()) { log.info(entry.getKey() + " " + entry.getValue()); } }
|
输出结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 2017-11-27 22:03:45,123 [Thread-0] INFO [map.ConcurrentMapDemo] - 0 - 0 2017-11-27 22:03:45,123 [Thread-1] INFO [map.ConcurrentMapDemo] - ADD:100 2017-11-27 22:03:45,248 [Thread-0] INFO [map.ConcurrentMapDemo] - 1 - 1 2017-11-27 22:03:45,357 [Thread-0] INFO [map.ConcurrentMapDemo] - 2 - 2 2017-11-27 22:03:45,466 [Thread-0] INFO [map.ConcurrentMapDemo] - 3 - 3 2017-11-27 22:03:45,575 [Thread-0] INFO [map.ConcurrentMapDemo] - 4 - 4 2017-11-27 22:03:45,685 [Thread-0] INFO [map.ConcurrentMapDemo] - 100 - 100 2017-11-27 22:03:48,135 [main] INFO [map.ConcurrentMapDemo] - -------- 2017-11-27 22:03:48,135 [main] INFO [map.ConcurrentMapDemo] - 0 0 2017-11-27 22:03:48,135 [main] INFO [map.ConcurrentMapDemo] - 1 1 2017-11-27 22:03:48,135 [main] INFO [map.ConcurrentMapDemo] - 2 2 2017-11-27 22:03:48,135 [main] INFO [map.ConcurrentMapDemo] - 3 3 2017-11-27 22:03:48,135 [main] INFO [map.ConcurrentMapDemo] - 4 4 2017-11-27 22:03:48,135 [main] INFO [map.ConcurrentMapDemo] - 100 100 Process finished with exit code 0
|
ConcurrentHashMap结构
我们来了解下ConcurrentHashMap的整体结构,这样有利于我们快速理解源码。我们就在此进行回顾下
HashMap的整体结构
HashMap底层使用数组和链表,实现哈希表结构。插入的元素通过散列的形式分布到数组的各个角标下;当有重复的散列值时,便将新增的元素插入在链表头部,使其形成链表结构,依次向后排列。
下面是,ConcurrentHashMap的结构:
与HashMap不同的是,ConcurrentHashMap中多了一层数组结构,由Segment和HashEntry两个数组组成。其中Segment起到了加锁同步的作用,而HashEntry则起到了存储K.V键值对的作用
在ConcurrentHashMap中,每一个ConcurrentHashMap都包含了一个Segment数组,在Segment数组中每一个Segment对象则又包含了一个HashEntry数组,而在HashEntry数组中,每一个HashEntry对象保存K-V数据的同时又形成了链表结构,此时与HashMap结构相同
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException { int sshift = 0; int ssize = 1; while (ssize < DEFAULT_CONCURRENCY_LEVEL) { ++sshift; ssize <<= 1; } int segmentShift = 32 - sshift; int segmentMask = ssize - 1; @SuppressWarnings("unchecked") Segment<K,V>[] segments = (Segment<K,V>[]) new Segment<?,?>[DEFAULT_CONCURRENCY_LEVEL]; for (int i = 0; i < segments.length; ++i) segments[i] = new Segment<K,V>(LOAD_FACTOR); s.putFields().put("segments", segments); s.putFields().put("segmentShift", segmentShift); s.putFields().put("segmentMask", segmentMask); s.writeFields(); Node<K,V>[] t; if ((t = table) != null) { Traverser<K,V> it = new Traverser<K,V>(t, t.length, 0, t.length); for (Node<K,V> p; (p = it.advance()) != null; ) { s.writeObject(p.key); s.writeObject(p.val); } } s.writeObject(null); s.writeObject(null); segments = null; }
|
在多线程中,每一个Segment对象守护了一个HashEntry数组,当对ConcurrentHashMap中的元素修改时,在获取到对应的Segment数组角标后,都会对此Segment对象加锁,之后再去操作后面的HashEntry元素,这样每一个Segment对象下,都形成了一个小小的HashMap,在保证数据安全性的同时,又提高了同步的效率。只要不是操作同一个Segment对象的话,就不会出现线程等待的问题
分享
新浪微博
QQ空间
QQ好友
豆瓣
Facebook
取消