面试题-Map线程安全相关


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;

image-20220105212545030

上图中, 如果多个线程写入的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"));
    }
}

6. ConcurrentHashMap如何并发扩容

7. ConcurrentHashMap 1.8 为何取消分段锁

8. ConcurrentHashMap 1.7 为什么使用lock锁而不是synchronized

9. ConcurrentHashMap 1.8 为什么使用synchronized而不是使用lock锁


文章作者: 王子
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 王子 !
评论
  目录