个人博客
欢迎访问个人博客: https://www.crystalblog.xyz/
备用地址: https://wang-qz.gitee.io/crystal-blog/
1. Hashtable与HashMap的区别
(1), HashMap实现不同步, 线程不安全. HashMap中的key-value都是存储在Entry中的.
Hashtable线程安全. 使用synchronized .
jdk7 底层数据结构: 数组 + 链表.
jdk8 底层数据结构: 数组 + 链表 + 红黑树.
(2), 继承不同. HashMap继承AbstractMap , Hashtable继承Dictionary类.
(3), Hashtable中的方法是同步的, 而HashMap中的方法在缺省情况下是非同步的, 在多线程并发环境下, 可以直接使用Hashtable. 但是要使用HashMap就要自己增加同步处理.
(4), HashMap中key和value允许为null.
Hashtable中, key和value都不允许为null值.
(5), 哈希值的使用不同, HashTable直接使用对象的hashCode值, HashMap是重新计算hash值.
2. Hashtable有哪些缺陷? 为什么很少使用Hashtable?
Hashtable底层使用 synchronized
保证线程安全性问题. 线程安全性问题: 加锁
synchronized 锁的升级过程: 偏向锁, 轻量锁, 重量锁.
(1), 传统Hashtable是采用synchronized 将整个Hashtable中的数组锁住, 但在多个线程情况下, 只允许一个线程访问put或get操作, 效率非常低, 但是能够保证线程安全.
(2), Jdk官方不推荐在多线程的情况下使用Hashtable或者HashMap, 建议使用ConcurrentHashMap
分段HashMap, 效率非常高.
核心思想: 减少多个线程下的锁竞争, 不会访问同一个Hashtable.
(3), Jdk8中, ConcurrentHashMap取消了分段锁设计, ConcurrentHashMap的get方法没有锁的竞争; Hashtable的get方法有锁的竞争.
上图中, A线程获取到this锁进行get操作, B线程的put就无法操作.
public class HashtableTest {
public static void main(String[] args) {
// a,b线程使用同一个Hashtable, 会发生this锁的竞争
Hashtable<Object, Object> hashtable = new Hashtable<>();
// a线程, get操作
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---" + hashtable.get("key"));
}
}, "a线程").start();
// b线程,put操作
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "---" + hashtable.put("key", "value"));
}
}, "b线程").start();
}
}
3. ConcurrentHashMap 1.7 底层实现
ConcurrentHashMap 将一个大的HashMap集合拆分成n多个不同的小Hashtable(Segment), 默认的情况下是分成16个不同的Segment. 每个Segment中都有自己独立的HashEntry<K,V>[] table;
上图中, 如果多个线程写入的key最终计算落地到不同的
小的Hashtable集合中, 就可以实现多线程同时写入key, 不发生锁的竞争. 但是如果多个线程写入的key最终落地到同一个
小的Hashtable集合中, 就会发生锁的竞争.
4. ConcurrentHashMap分段锁设计
ConcurrentHashMap 底层采用分段锁设计, 将一个大的Hashtable线程安全的集合拆分成多个小的Hashtable集合,默认初始化16个
小的Hashtable集合.
如果同时16个线程最终计算的index值落地到不同的小的Hashtable集合, 不会发生锁的竞争, 同时可以支持16个线程访问ConcurrentHashMap写的操作, 效率非常高.
Jdk7中, ConcurrentHashMap需要计算出2次index值:
第一次计算index值, 计算key存放到具体小的Hashtable
第二次计算index值, 计算key存放到具体小的Hashtable中对应具体的index值
5. 如何手写出一个ConcurrentHashMap
public class MyConcurrentHashMap<K, V> {
private Hashtable<K, V>[] hashtables;
public MyConcurrentHashMap() {
// 初始化16个长度的Hashtable数组
hashtables = new Hashtable[16];
for (int i = 0; i < hashtables.length; i++) {
hashtables[i] = new Hashtable<K, V>();
}
}
// put方法
public void put(K k, V v) {
// 根据hashcode计算存放的index
int hashtableIndex = k.hashCode() % hashtables.length;
hashtables[hashtableIndex].put(k, v);
}
// get方法
public V get(K k) {
// 根据hashcode获取数据存放的index
int hashtableIndex = k.hashCode() % hashtables.length;
return hashtables[hashtableIndex].get(k);
}
// 测试
public static void main(String[] args) {
MyConcurrentHashMap<String, String> concurrentHashMap = new MyConcurrentHashMap<>();
concurrentHashMap.put("m", "mv");
concurrentHashMap.put("k", "kv");
System.out.println(concurrentHashMap.get("m") + "--" + concurrentHashMap.get("k"));
}
}