面试题答案
一键面试整体设计思路
- 分段锁设计:借鉴ConcurrentHashMap的分段锁思想,将整个Hashtable分成多个段(Segment),每个段独立加锁,这样不同段上的读写操作可以并发执行,从而提高并发性能。
- 减少锁粒度:通过缩小锁的范围,使得在高并发情况下,竞争同一把锁的概率降低,提升系统的吞吐量。
关键数据结构
- Segment类:
- 继承自ReentrantLock,每个Segment对象代表一个锁,同时包含一个HashEntry数组,用于存储键值对。
-
static final class Segment<K, V> extends ReentrantLock { transient volatile HashEntry<K, V>[] table; // 其他必要的属性和方法 }
- HashEntry类:
- 类似于Hashtable中的Entry,用于存储键值对。不同的是,这里将其设计为不可变类,并且使用volatile修饰next指针,保证多线程环境下的可见性。
-
static final class HashEntry<K, V> { final K key; final int hash; volatile V value; volatile HashEntry<K, V> next; HashEntry(K key, int hash, V value, HashEntry<K, V> next) { this.key = key; this.hash = hash; this.value = value; this.next = next; } }
- 自定义的ConcurrentHashtable类:
- 包含一个Segment数组,以及一些控制并发读写的属性和方法。
-
public class ConcurrentHashtable<K, V> { private final Segment<K, V>[] segments; private static final int DEFAULT_CONCURRENCY_LEVEL = 16; public ConcurrentHashtable() { this(DEFAULT_CONCURRENCY_LEVEL); } public ConcurrentHashtable(int concurrencyLevel) { segments = new Segment[concurrencyLevel]; for (int i = 0; i < concurrencyLevel; i++) { segments[i] = new Segment<>(); } } // 其他方法 }
方法的实现细节
- put方法:
- 计算键的哈希值,根据哈希值定位到对应的Segment。
- 锁定该Segment,然后在Segment的HashEntry数组中查找是否存在相同键的元素。
- 如果存在则更新值,否则插入新的HashEntry。
- 操作完成后释放锁。
-
public V put(K key, V value) { int hash = key.hashCode(); int segmentIndex = hash & (segments.length - 1); Segment<K, V> segment = segments[segmentIndex]; segment.lock(); try { HashEntry<K, V>[] table = segment.table; int index = hash & (table.length - 1); 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; } } // 插入新元素 segment.put(new HashEntry<>(key, hash, value, table[index])); return null; } finally { segment.unlock(); } }
- get方法:
- 计算键的哈希值,定位到对应的Segment。
- 无需锁定Segment,直接在Segment的HashEntry数组中查找键对应的值。
- 由于HashEntry的value和next是volatile修饰的,能保证可见性,所以无需加锁也能获取到最新值。
-
public V get(K key) { int hash = key.hashCode(); int segmentIndex = hash & (segments.length - 1); Segment<K, V> segment = segments[segmentIndex]; HashEntry<K, V>[] table = segment.table; int index = hash & (table.length - 1); for (HashEntry<K, V> e = table[index]; e!= null; e = e.next) { if (e.key.equals(key)) { return e.value; } } return null; }