除了加锁,Hashtable和HashMap还有不同吗?( 二 )


假设它们允许 null key 和 null value,我们来看看会出现什么问题:当你通过 get(key) 获取到对应的 value 时,如果返回的结果是 null 时,你无法判断这个 key 是否真的存在 。为此,我们需要调用方法来判断这个 key 到底是 value = http://www.kingceram.com/post/null 还是它根本就不存在,如果方法返回的结果是 true,OK,那我们就可以调用 map.get(key) 获取 value 。
但是注意,这仅仅是在单线程的情况下!!
由于和是支持多线程的容器,在调用 map.get(key) 的这个时候 map 对象可能已经不同了 。
比如说某个线程 A 调用了 map.get(key) 方法,它返回为 value = http://www.kingceram.com/post/null 的真实情况就是因为这个 key 不能存在 。当然,线程 A 还是会按部就班的继续用 map.(key),我们期望的结果是返回 false 。
但是如果在线程 A 调用 map.get(key) 方法之后,map. 方法之前,另一个线程 B 执行了 map.put(key,null) 的操作 。那么线程 A 调用的 map. 方法返回的就是 true 了 。这就与我们的假设的真实情况不符合了 。
所以为了保证并发情况的安全性,和不允许 key 和 value 为 null
和的区别
除了不允许 null key 和 null value 而允许以外,它俩还有以下几点不同:
1)初始化容量不同: 的初始容量为 16,初始容量为 11 。两者的负载因子默认都是 0.75;
2)扩容机制不同:当现有容量大于总容量 * 负载因子时,扩容规则为当前容量翻倍,扩容规则为当前容量翻倍 + 1;
3)迭代器不同:首先,的迭代器 是fail-fast(快速失败)的,而的迭代器是fail-safe(失败安全)的
fail-fast原理
当迭代器在遍历容器中的元素时,会维护一个变量,在遍历集合过程中,如果某个元素发生变化,的值也会发生改变 。这就是为什么是线程不安全的 。
【除了加锁,Hashtable和HashMap还有不同吗?】因为在使用()/next()方法时,首先会拿元素的和一个值比较,如果两者相等就返回这个元素的值,否则返回一个错误 。
(**这里异常的抛出条件是检测到 != 这个条件 。**如果集合发生变化时修改值刚好又设置为了值,则异常不会抛出 。这就是为什么线程不安全)
fail-safe原理
和fail-fast不同,fail-safe机制在遍历集合元素时,如果此时对集合结构修改,**fail-safe机制会先重新复制一份原集合的元素出来,然后迭代器遍历的是这个原集合的副本!**这也是为什么支持多线程了 。但是fail-safe也有缺点:
1.复制原集合需要额外的空间和时间开销
2.无法保证遍历的是最新的内容 。
.7
存储结构
Java 7 中的存储结构如上图,由很多个组合,而每一个是一个类似于的结构,所以每一个的内部可以进行扩容 。但是的个数一旦初始化就不能改变,默认的个数是 16 个,你也可以认为默认支持最多 16 个线程并发 。
初始化
通过的无参构造探寻的初始化流程 。
/*** Creates a new, empty map with a default initial capacity (16),* load factor (0.75) and concurrencyLevel (16).*/public ConcurrentHashMap() {this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_CONCURRENCY_LEVEL);}
无参函数中调用了有参构造,总共有三个参数
/*** 默认初始化容量*/static final int DEFAULT_INITIAL_CAPACITY = 16;/*** 默认负载因子*/static final float DEFAULT_LOAD_FACTOR = 0.75f;/*** 默认并发级别*/static final int DEFAULT_CONCURRENCY_LEVEL = 16;
总结一下在 Java 7 中的初始化逻辑 。
1.必要参数校验 。
2.校验并发级别大小,如果大于最大值,重置为最大值 。无参构造默认值是 16.