HashTable
讲ConcurrentHashMap之前,稍微先说说HashTable。
HashTable实现并发的方式,简单一句话总结就是,加synchronized同步锁。
查看一下HashTable的源码,到处可见的synchronized关键字。
/**
* Returns the number of keys in this hashtable.
*
* @return the number of keys in this hashtable.
*/
public synchronized int size() {
return count;
}
/**
* Tests if this hashtable maps no keys to values.
*
* @return <code>true</code> if this hashtable maps no keys to values;
* <code>false</code> otherwise.
*/
public synchronized boolean isEmpty() {
return count == 0;
}
public synchronized boolean contains(Object value) {
if (value == null) {
throw new NullPointerException();
}
Entry<?,?> tab[] = table;
for (int i = tab.length ; i-- > 0 ;) {
for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
if (e.value.equals(value)) {
return true;
}
}
}
return false;
}
.......
这种实现方式,优点当然就是简单粗暴有效,缺点也很明显,同一时间只要某一个线程操作数据,整个HashTable会被锁定,其他任何线程都无法操作该HashTable,只能等待之前线程执行完或者释放锁。显而易见,这种方式很粗暴,效率很低。
1.7版本的ConcurrentHashMap
1.7版本的ConcurrentHashMap主要策略是Segment分段锁。当某个线程访问一段数据时,会锁定一定范围内的数据,因此ConcurrentHashMap内会存在多个锁定区间锁。如果多线程访问不同数据段的数据时,相互之间无影响,这样能提高并发性能。
1.8版本
1.8版本的ConcurrentHashMap,主要是有如下改动
1.将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构.(与HashMap的变化基本一致)
2.取消segments字段,直接采用transient volatile HashEntry<K,V>[] table保存数据,采用table数组元素作为锁,从而实现了对每一行数据进行加锁,并发控制使用synchronized和CAS来操作,synchronized只锁定当前链表或红黑树的首节点,这样只要哈希不冲突(不操作同一位置元素),就不会产生并发,效率又提升很多。
CAS(Compare And Swap,比较交换)算法,它包含三个参数 CAS(V, E, N): V 表示要更新的变量, E 表示预期值, N 表示新值。基本思想就是不断地去比较当前内存中的变量值与你指定的一个变量值(预期值)是否相等,如果相等,则接受你指定的修改的值(新值),否则证明已经有别的线程修改过该变量的值,拒绝你的操作。因为当前线程中的值已经不是最新的值,你的修改很可能会覆盖掉其他线程修改的结果。这一点与乐观锁,SVN的思想是比较类似的。
以上部分内容参考文献:
https://juejin.cn/post/6976988436032913438