ConcurrentHashMap其采用分段锁设计,将一个大的线程安全的HashTable集合拆分成n多个小的HashTable集合,默认初始化16个小的HashTable集合。锁竞争概率非常小(即线程阻塞概率非常小)
JDK1.7的index需要计算两次。
ConcurrentHashMap是Java提供的线程安全的哈希表,它在多线程环境下提供高效的并发操作。它的底层实现原理是基于分段锁(Segment)和CAS(Compare and Swap)操作。
1. 分段锁(Segment):ConcurrentHashMap将整个存储空间分为多个Segment,每个Segment维护着一个子哈希表。每个Segment都拥有自己的锁,不同的线程可以独立地操作不同的Segment,从而实现了并发操作。每个Segment只锁定当前操作的部分,而不必锁定整个数据结构。
2. 哈希桶(Hash Bucket):每个Segment内部维护着一个哈希桶数组,用于存储键值对。每个哈希桶是一个链表或红黑树结构,用于解决哈希冲突。当链表长度超过阈值(默认为8)时,链表会转换为红黑树,提高查找和删除的效率。
3. CAS操作:ConcurrentHashMap使用CAS操作(无锁操作)来保证线程安全性。CAS是一种乐观锁的机制,它比传统的锁机制有更低的开销。CAS操作通过比较当前值和期望值是否相等来决定是否更新值,如果相等,则更新成功;如果不相等,则尝试重新读取和更新。CAS操作是原子的,当多个线程同时访问同一个Segment时,可以通过CAS来确保并发的安全性。
4. 扩容机制:ConcurrentHashMap在插入过程中会检查当前Segment的容量是否超过了阈值(默认为0.75)。如果超过了阈值,则会触发扩容操作。此时,会对整个ConcurrentHashMap进行扩容,并重新计算哈希值分布。实现过程中,只需要对每个Segment进行扩容操作,而不需要停止其他线程的访问。
通过分段锁和CAS操作的结合,ConcurrentHashMap实现了高效的并发操作。多个线程可以同时对不同的Segment进行访问,提高了并发性能。与HashTable相比,ConcurrentHashMap在并发性能、扩展性和灵活性方面有了显著的提升。
问题一:HashMap与HashTable的区别?
HashMap和HashTable都是常见的哈希表数据结构,用于在键值对之间建立映射关系。它们的区别如下:
1. 线程安全性:HashTable是线程安全的,而HashMap不是。HashTable的方法是同步的,可以在多线程环境下使用,但性能较差。HashMap则不提供线程安全的保证,可以在单线程环境下使用,性能较好。
2. NULL键和NULL值:HashMap允许使用null作为键和值,而HashTable不允许。如果尝试在HashTable中插入null键或值,将会抛出NullPointerException。
3. 继承关系:HashMap是AbstractMap类的子类,而HashTable是Dictionary类的子类。Dictionary是Java早期提供的一个已经被废弃的类。
4. 迭代器:HashMap的迭代器是fail-fast的,即在迭代过程中如果其他线程修改了HashMap的结构(增加、删除元素),将会抛出ConcurrentModificationException。HashTable不支持fail-fast机制。
5. 初始容量和扩容机制:HashMap初始容量默认为16,扩容时容量会自动翻倍。HashTable初始容量默认为11,扩容时容量会增加大约一倍加一,并重新计算哈希值分布。
综上所述,HashMap在性能、灵活性、扩展性方面优于HashTable,因此在单线程环境下推荐使用HashMap。如果需要在多线程环境下使用,可以考虑使用ConcurrentHashMap来替代HashTable。
问题二:HashTable的缺陷?
虽然HashTable在一些特定的场景中具有一些优势,但它也存在一些明显的缺陷:
1. 线程安全性开销:HashTable在实现上使用了synchronized关键字来保证线程安全性。这意味着在多线程环境下,对于非常频繁的读写操作,需要进行加锁和解锁操作,会导致性能下降。相对而言,其他线程安全的集合类(如ConcurrentHashMap)提供了更好的性能。
2. 低效的迭代器:HashTable的迭代器是通过Enumeration实现的,而不是Iterator。Enumeration的功能比较受限,只能进行遍历,不支持移除操作。同时,HashTable的迭代器不支持fail-fast机制,当在迭代过程中其他线程修改了HashTable的结构,可能会导致遍历失败或遍历到不一致的元素。
3. 存在数据冲突:HashTable使用内部哈希函数来将键映射为哈希值,然后通过哈希值将数据分散存储在不同的槽中。然而,由于哈希函数的限制,不同的键可能会产生相同的哈希值,这就是哈希冲突。HashTable使用开放地址法来解决冲突,但可能导致性能下降和数据分布不均匀。
4. 不支持null键和null值:HashTable不允许使用null作为键或值,如果尝试插入null键或值,会抛出NullPointerException。这在某些情况下可能会限制程序的灵活性和简洁性。
由于上述缺陷,Java推荐使用并发安全的HashMap的替代方案,如ConcurrentHashMap来满足多线程环境下的需求。
三:手写出ConcurrentHashMap
package lzx6;
import java.util.Hashtable;
public class MyConCurrentHashMap<K,V> {
private Hashtable<K,V>[] hashtables;
public MyConCurrentHashMap(){
hashtables = new Hashtable[16];
for (int i = 0; i < hashtables.length; i++) {
hashtables[i] = new Hashtable<>();
}
}
public void put(K k,V v){
int hashTableIndex = k.hashCode()%hashtables.length;
hashtables[hashTableIndex].put(k,v);
}
public V get(K k){
int hashTableIndex = k.hashCode()%hashtables.length;
return hashtables[hashTableIndex].get(k);
}
public static void main(String[] args) {
MyConCurrentHashMap<String, String> myConCurrentHashMap = new MyConCurrentHashMap<>();
myConCurrentHashMap.put("lzx","yy");
myConCurrentHashMap.put("我爱你呀","yy");
System.out.println(myConCurrentHashMap.get("我爱你呀"));
}
}
运行结果: