1 map的分类
(1)非线程安全的map:
HashMap
(2)线程安全的map:
HashTable Collections.synchronizedMap(new HashMap<K, V>()) ConcurrentHashMap
2 各个map的比较
(1)HashMap:非线程安全的,适用于单线程环境,因为不存在锁和阻塞,所以效率较高
(2)HashTable:线程安全的,使用synchronized关键字修饰方法,实现了线程安全。单线程环境下,建议使用HashMap,因为锁的管理也需要开销。
(3)
Collections.synchronizedMap(map):线程安全的,使用Collections里面的静态方法将非线程安全的map封装成线程安全的map。效率和应用场景方面与HashTable相同。
(4)
ConcurrentHashMap:一个专用于高并发的map。使用了分段锁机制,减小锁粒度,因此提高了并发度。
3 ConcurrentHashMap浅析
3.1 底层存储结构图
3.2 分段锁
ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的hash table,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
ConcurrentHashMap是专门为线程并发而设计的,它的get操作是无锁的,它的put操作只在对应的segment上加锁。因此,它的整体性能优于同步的HashMap(对整个table加锁)。
默认情况下,ConcurrentHashMap拥有16个段,因此,足够幸运的话,可以同时接受16个线程同时put(插入到不同段中)。
3.3 弱一致性问题
由于get方法,并没有加锁,因此肯定会存在一致性的问题。get方法并没有加锁,因此也不会和put方法冲突,当多个线程同时操作该map时,有的线程get,有的线程put,因为我们并不确定put方法执行完了没(可能只执行了一部分语句,后面真正改内存的语句还没有执行),所以get不一定能够得到最新值(put还没写进去,叫最新值也不是很妥当)。但是,一旦put操作执行完,那么get一定可以感知到最新值,因为Node的val和next字段都是volatile的:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
volatile V val;
volatile Node<K,V> next;
}
同理,很多全局方法都存在弱一致性问题,比如size等。
ConcurrentHashMap的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。要成为强一致性,就得到处使用锁,甚至是全局锁。
上面仅是个人的一点浅见,有不妥之处欢迎指出。