MST

星途 面试题库

面试题:自定义实现一个类似ConcurrentHashMap的多线程集合类

假设在Java中没有ConcurrentHashMap类,要求你自定义实现一个支持高效并发访问的哈希表结构。请描述你的设计思路,包括如何实现线程安全、如何进行分段锁设计以提高并发性能、如何处理哈希冲突等关键问题,并给出核心代码示例。
44.8万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 线程安全:采用分段锁机制,对不同的数据段进行锁定,从而允许不同线程同时访问不同的数据段,减少锁竞争。
  2. 分段锁设计:将哈希表分成多个段(Segment),每个段有自己独立的锁。当对某个键值对进行操作时,首先通过哈希算法确定该键值对属于哪个段,然后获取该段的锁进行操作。
  3. 处理哈希冲突:采用链地址法,即每个哈希桶(bucket)是一个链表,当发生哈希冲突时,将新的节点添加到链表末尾。

核心代码示例

import java.util.concurrent.locks.ReentrantLock;

class MyConcurrentHashMap<K, V> {
    private static final int DEFAULT_SEGMENTS = 16;
    private final Segment<K, V>[] segments;

    public MyConcurrentHashMap() {
        this.segments = new Segment[DEFAULT_SEGMENTS];
        for (int i = 0; i < DEFAULT_SEGMENTS; i++) {
            segments[i] = new Segment<>();
        }
    }

    private static class Segment<K, V> {
        private final ReentrantLock lock = new ReentrantLock();
        private final HashEntry<K, V>[] table;

        @SuppressWarnings("unchecked")
        Segment() {
            table = new HashEntry[16];
        }

        V put(K key, V value) {
            lock.lock();
            try {
                int hash = key.hashCode();
                int index = (hash & 0x7FFFFFFF) % table.length;
                for (HashEntry<K, V> e = table[index]; e != null; e = e.next) {
                    if (e.key.equals(key)) {
                        V oldValue = e.value;
                        e.value = value;
                        return oldValue;
                    }
                }
                HashEntry<K, V> newEntry = new HashEntry<>(key, value, table[index]);
                table[index] = newEntry;
                return null;
            } finally {
                lock.unlock();
            }
        }

        V get(K key) {
            lock.lock();
            try {
                int hash = key.hashCode();
                int index = (hash & 0x7FFFFFFF) % table.length;
                for (HashEntry<K, V> e = table[index]; e != null; e = e.next) {
                    if (e.key.equals(key)) {
                        return e.value;
                    }
                }
                return null;
            } finally {
                lock.unlock();
            }
        }
    }

    private static class HashEntry<K, V> {
        final K key;
        V value;
        HashEntry<K, V> next;

        HashEntry(K key, V value, HashEntry<K, V> next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
    }

    public V put(K key, V value) {
        int segmentIndex = (key.hashCode() & 0x7FFFFFFF) % segments.length;
        return segments[segmentIndex].put(key, value);
    }

    public V get(K key) {
        int segmentIndex = (key.hashCode() & 0x7FFFFFFF) % segments.length;
        return segments[segmentIndex].get(key);
    }
}

可以通过以下方式测试:

public class TestMyConcurrentHashMap {
    public static void main(String[] args) {
        MyConcurrentHashMap<String, Integer> map = new MyConcurrentHashMap<>();
        map.put("key1", 1);
        System.out.println(map.get("key1"));
    }
}

在上述代码中:

  • MyConcurrentHashMap 类包含一个 Segment 数组,每个 Segment 管理一部分哈希表数据。
  • Segment 类使用 ReentrantLock 实现锁机制,并且包含一个 HashEntry 数组用于存储键值对,通过链地址法处理哈希冲突。
  • HashEntry 类表示哈希表中的节点,包含键、值和指向下一个节点的引用。
  • putget 方法首先确定要操作的 Segment,然后调用 Segment 相应的方法进行操作。