【转载 http://www.bootdo.com/blog/open/post/130 】
相信每个JAVA程序员都了解HashMap,最大的问题是线程不安全,因为方法中不涉及到同步,也正因为如此,HashMap的效率非常高,在不涉及线程安全的程序中广泛被应用。然而当涉及到多线程作业时,就会出现一些问题。为了解决这些问题JAVA提供了Hashtable,这是一种整体加锁的数据结构,然而效率不敢恭维。这时候就有了ConcurrentHashMap。
一个例子说明三者关系:
前提:某个卫生间共有16个隔间。
HashMap:每个隔间都没锁门,有人想上厕所,管理员指给他一个隔间,里面没人的话正常用,里面有人的话把这个人赶出来然后用。
优点,每个人进来不耽误都能用;缺点,每一个上厕所的人都有被中途赶出来的危险。
Hashtable:在卫生间外面安装一个大门,有人想上厕所,问管理员要一个钥匙进门,把门反锁用,用完后出来,把钥匙交换给管理员。在这个人上厕所期间,其他所有人都必须在外面排号。
优点,每个人都能安心上完厕所;缺点,卫生间外面可能已经出了人命。 =_=
ConcurrentHashMap:在卫生间每个隔间安装门锁,有人想上厕所,管理员指给他一个隔间,进来后这个隔间如果没人在用则直接用,如果有人正在用,则排号。在这期间其他人会按规则分到不同的隔间,重复上述行为。
优点:每个人都能安心上厕所,外面排队的也被均匀分摊。缺点:。。。
ConcurrentHashMap实现的原理
ConcurrentHashMap把Map分成了N个Segment(默认16),其中Segment是线程同步的,相当于分成了N个Hashtable。当实现Put方法时,在key值经过正常的hash后,还要再经过一次segmentForHash算法,用来分配具体防盗哪个Segment。后来的线程如果经过计算也是放在这个Segment下,则需要先获取锁,如果计算得出应该放在其他的Segment,则正常执行,不会影响效率,以此实现线程安全。ConcurrentHashMap使用锁分离技术,只要多个修改操作不发生在同一个Segment上,它们就可以并发进行。
有些方法需要跨段,比如size()和containsValue(),需要锁定整个表而而不仅仅是某个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。这里“按顺序”是很重要的,否则极有可能出现死锁,在ConcurrentHashMap内部,段数组是final的,并且其成员变量实际上也是final的,但是,仅仅是将数组声明为final的并不保证数组成员也是final的,这需要实现上的保证。这可以确保不会出现死锁,因为获得锁的顺序是固定的。