HashTable 底层 通过 synchronized 保证线程安全性问题
保证线程安全性问题—加上锁—发生锁的竞争
HashTable 当多个线程 在访问 get或者put操作的时候会发生this锁的竞争,多个线程竞争锁 最终只会有一个线程获取到this锁,获取不到的this锁 可能会阻塞等待。最终将我们的 HashTable 中 get 或者put方法改成单线程执行 效率是非常的低。
在多线程的情况下 不推荐使用 HashTable
而是推荐使用高效率的 ConcurrentHashMap
使用传统HashTable保证线程问题,是采用synchronized锁将整个HashTable中的数组锁住,在多个线程中只允许一个线程访问Put或者Get,效率非常低,但是能够保证线程安全问题。
Jdk官方不推荐在多线程的情况下使用HashTable或者HashMap,建议使用ConcurrentHashMap分段HashMap,效率非常高。
ConcurrentHashMap 核心思想:减少多个线程锁竞争 不会在访问同一个HashTable
就是将一个大的HashTable集合拆分成n多个小的HashTable集合 默认16个(使用分段锁设计)
在多线程的情况下访问到我们的ConcurrentHashMap 1.7版本做写的操作,如果多个线程写入的key 最终计算落地到不同小的HashTable集合中,就可以实现多线程同时写入key 不会发生锁的竞争。
如果多个线程写入的key 最终计算落地到同一个小的HashTable集合中,就会发生锁的竞争。
ConcurrentHashMap get方法没有锁的竞争
HashTable get方法有锁的竞争
ConcurrentHashMap将一个大的HashTable集合拆分成n多个不同的小的HashTable(Segment),默认的情况下是分成16个不同的Segment。每个Segment中都有自己独立的HashEntry
手写 ConcurrentHashMap 1.7 :
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("xinshang","mengmeng");
System.out.println(myConcurrentHashMap.get("xinshang"));
}
}
ConcurrentHashMap 底层采用 分段锁设计 将一个大的HashTable线程安全的集合拆分成n多个小的 HashTable集合,默认初始化16个小的HashTable集合。
如果同时16个线程 最终计算index值 落地到不同的小的HashTable集合 不会发生锁的竞争,同时可以支持16个线程 访问ConcurrentHashMap 写的操作,效率非常高。
注意点:ConcurrentHashMap 1.7版本需要计算出2次 index值。
在 JDK 1.7 中,ConcurrentHashMap 虽然是线程安全的,但因为它的底层实现是数组 + 链表的形式,所以在数据比较多的情况下访问是很慢的,因为要遍历整个链表,而 JDK 1.8 则使用了数组 + 链表/红黑树的方式优化了 ConcurrentHashMap 的实现,具体实现结构如下:
链表升级为红黑树的规则:当链表长度大于 8,并且数组的长度大于 64 时,链表就会升级为红黑树的结构。
ConcurrentHashMap 1.8版本 取消了分段锁设计,改成了直接锁表头(数组+链表+红黑树)
在 JDK 1.8 中,ConcurrentHashMap 是在头节点加锁来保证线程安全的,锁的粒度相比 Segment 来说更小了,发生冲突和加锁的频率降低了,并发操作的性能就提高了。而且 JDK 1.8 使用的是红黑树优化了之前的固定链表,那么当数据量比较大的时候,查询性能也得到了很大的提升,从之前的 O(n) 优化到了 O(logn) 的时间复杂度,具体加锁示意图如下: