HashMap、HashTable、ConcurrentHashMap区别

来源:互联网 发布:孙鑫java视频教程 编辑:程序博客网 时间:2024/05/19 13:08

一、Hashtable和HashMap都实现了Map接口,但是Hashtable的实现是基于Dictionary抽象类

public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable {public class Hashtable<K,V>    extends Dictionary<K,V>implements Map<K,V>, Cloneable, java.io.Serializable {public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>implements ConcurrentMap<K,V>, Serializable {

数组:数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;

链表:链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。

(1)在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null

(2)当get()方法返回null值时,即可以表示 HashMap中没有该键,也可以表示该键所对应的值为null

(3)HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。而在Hashtable中,无论是key还是value都不能为null

二、HashMap和Hashtable的区别

(1)都属于Map接口的类,实现了将惟一键映射到特定的值上。

(2)HashMap 类没有分类或者排序。它允许一个 null 键和多个 null 值。

(3)Hashtable 类似于 HashMap,但是不允许 null 键和 null 值。它也比 HashMap 慢,因为它是同步的。

这里写图片描述

三、并发下的HashMap

因为多线程环境下,使用Hashmap进行put操作会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用HashMap

实例:

final HashMap<String, String> map = new HashMap<String, String>(2);        Thread t = new Thread(new Runnable() {            public void run() {                for (int i = 0; i < 10000; i++) {                    new Thread(new Runnable() {                        public void run() {                            map.put(UUID.randomUUID().toString(), "");                        }                    }, "ftf" + i).start();                }            }        }, "ftf");        t.start();        t.join();

四、效率低下的HashTable容器

HashTable容器使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。

五、ConcurrentHashMap的锁分段技术

HashTable容器在竞争激烈的并发环境下表现出效率低下的原因,是因为所有访问HashTable的线程都必须竞争同一把锁,那假如容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

六、ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成

Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构, ConcurrentHashMap 在默认并发级别会创建包含 16 个 Segment 对象的数组。每个 Segment 的成员对象 table 包含若干个散列表的桶。每个桶是由 HashEntry 链接起来的一个链表。

这里写图片描述

1、get操作:Segment的get操作实现非常简单和高效。先经过一次再哈希,然后使用这个哈希值通过哈希运算定位到segment,再通过哈希算法定位到元素,get操作的高效之处在于整个get过程不需要加锁,除非读到的值是空的才会加锁重读

2、put操作:由于put方法里需要对共享变量进行写入操作,所以为了线程安全,在操作共享变量时必须得加锁。Put方法首先定位到Segment,然后在Segment里进行插入操作。插入操作需要经历两个步骤,第一步判断是否需要对Segment里的HashEntry数组进行扩容,第二步定位添加元素的位置然后放在HashEntry数组里。

3、如何扩容:扩容的时候首先会创建一个两倍于原容量的数组,然后将原数组里的元素进行再hash后插入到新的数组里。为了高效ConcurrentHashMap不会对整个容器进行扩容,而只对某个segment进行扩容。

4、size操作:因为在累加count操作过程中,之前累加过的count发生变化的几率非常小,所以ConcurrentHashMap的做法是先尝试2次通过不锁住Segment的方式来统计各个Segment大小,如果统计的过程中,容器的count发生了变化,则再采用加锁的方式来统计所有Segment的大小。

5、判断:那么ConcurrentHashMap是如何判断在统计的时候容器是否发生了变化呢?使用modCount变量,在put , remove和clean方法里操作元素前都会将变量modCount进行加1,那么在统计size前后比较modCount是否发生变化,从而得知容器的大小是否发生变化。

阅读全文
1 0
原创粉丝点击