MST
星途 面试题库

面试题:Java Hashtable多线程场景下自定义锁机制的实现与优化

假设你不能使用Java现有的并发集合类,需要基于Hashtable在多线程环境下自定义一套锁机制来保证数据的一致性和高效访问。请详细描述你的设计思路,包括锁的粒度控制、死锁预防等方面,并且给出关键部分的代码实现及优化方案。
22.7万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

设计思路

  1. 锁的粒度控制
    • 考虑采用细粒度锁。对于Hashtable,我们可以将整个表按照一定规则(比如按照哈希值的范围)进行分区。每个分区有自己独立的锁,这样不同线程在访问不同分区的数据时,不会相互阻塞,提高并发度。
    • 例如,我们可以将哈希表的桶按照哈希值的范围分成多个区间,每个区间对应一个锁。这样当多个线程访问不同区间的元素时,它们可以并行操作,而不会因为竞争同一把锁而产生阻塞。
  2. 死锁预防
    • 为了预防死锁,我们需要确保所有线程按照相同的顺序获取锁。一种简单的方法是对所有锁进行编号,线程在获取多个锁时,必须按照从小到大的顺序获取锁。
    • 例如,我们可以为每个分区锁分配一个唯一的ID,线程在尝试获取多个锁时,先对这些锁的ID进行排序,然后按照升序依次获取锁,这样就避免了死锁的发生。

关键部分代码实现

import java.util.Hashtable;

class CustomConcurrentHashtable<K, V> {
    private static final int DEFAULT_PARTITIONS = 16;
    private final Hashtable<K, V>[] tables;
    private final Object[] locks;

    public CustomConcurrentHashtable() {
        this(DEFAULT_PARTITIONS);
    }

    public CustomConcurrentHashtable(int partitions) {
        tables = new Hashtable[partitions];
        locks = new Object[partitions];
        for (int i = 0; i < partitions; i++) {
            tables[i] = new Hashtable<>();
            locks[i] = new Object();
        }
    }

    private int getPartition(K key) {
        return Math.abs(key.hashCode()) % tables.length;
    }

    public V get(K key) {
        int partition = getPartition(key);
        synchronized (locks[partition]) {
            return tables[partition].get(key);
        }
    }

    public V put(K key, V value) {
        int partition = getPartition(key);
        synchronized (locks[partition]) {
            return tables[partition].put(key, value);
        }
    }

    public V remove(K key) {
        int partition = getPartition(key);
        synchronized (locks[partition]) {
            return tables[partition].remove(key);
        }
    }
}

优化方案

  1. 动态调整分区数量:可以根据运行时的负载情况动态调整分区数量。当发现某个分区的锁竞争非常激烈时,可以适当增加分区数量,将该分区的数据分散到更多的分区中,减少锁竞争。
  2. 读写锁优化:对于读操作远多于写操作的场景,可以使用读写锁代替普通的互斥锁。读操作可以并发执行,而写操作则需要独占锁,这样可以进一步提高并发性能。例如,使用Java的ReentrantReadWriteLock替换当前的synchronized块。
import java.util.Hashtable;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class OptimizedCustomConcurrentHashtable<K, V> {
    private static final int DEFAULT_PARTITIONS = 16;
    private final Hashtable<K, V>[] tables;
    private final ReentrantReadWriteLock[] locks;

    public OptimizedCustomConcurrentHashtable() {
        this(DEFAULT_PARTITIONS);
    }

    public OptimizedCustomConcurrentHashtable(int partitions) {
        tables = new Hashtable[partitions];
        locks = new ReentrantReadWriteLock[partitions];
        for (int i = 0; i < partitions; i++) {
            tables[i] = new Hashtable<>();
            locks[i] = new ReentrantReadWriteLock();
        }
    }

    private int getPartition(K key) {
        return Math.abs(key.hashCode()) % tables.length;
    }

    public V get(K key) {
        int partition = getPartition(key);
        locks[partition].readLock().lock();
        try {
            return tables[partition].get(key);
        } finally {
            locks[partition].readLock().unlock();
        }
    }

    public V put(K key, V value) {
        int partition = getPartition(key);
        locks[partition].writeLock().lock();
        try {
            return tables[partition].put(key, value);
        } finally {
            locks[partition].writeLock().unlock();
        }
    }

    public V remove(K key) {
        int partition = getPartition(key);
        locks[partition].writeLock().lock();
        try {
            return tables[partition].remove(key);
        } finally {
            locks[partition].writeLock().unlock();
        }
    }
}