1

在一次采访中,面试官询问了并发 Hash Map 及其功能,我对此进行了详细解释。他说,在并发 HashMap 更新操作的情况下,ConcurrentHashMap 只锁定 Map 的一部分,而不是整个 Map。

所以他让我写一个简单的程序来证明,在更新操作过程中,ConcurrentHashMap 只锁定了 Map 的一部分,而不是整个 Map。我无法做到这一点,所以请告诉我如何实现这一点。

4

1 回答 1

4

面试官可能期待一个简单的答案,例如:

  • 如果整个地图对于 get/put 操作是同步的,添加线程不会提高吞吐量,因为瓶颈将是同步块。然后你可以用 synchronizedMap 写一段代码,显示添加线程没有帮助
  • 因为映射使用了多个锁,并且假设您的机器上有多个内核,添加线程将提高吞吐量

下面的示例输出以下内容:

同步一线程:30
同步多线程:96
并发一线程:219
并发多线程:142

因此,您可以看到同步版本在高争用(16 个线程)下慢 3 倍以上,而并发版本在多线程下几乎是单线程速度的两倍。

值得注意的是,ConcurrentMap 在单线程情况下具有不可忽略的开销。

这是一个非常人为的例子,所有可能的问题都归因于微基准测试(无论如何都应该丢弃第一个结果)。但它暗示了会发生什么。

public class Test1 {
    static final int SIZE = 1000000;
    static final int THREADS = 16;
    static final ExecutorService executor = Executors.newFixedThreadPool(THREADS);

    public static void main(String[] args) throws Exception{

        for (int i = 0; i < 10; i++) {
            System.out.println("Concurrent one thread");
            addSingleThread(new ConcurrentHashMap<Integer, Integer> ());
            System.out.println("Concurrent multiple threads");
            addMultipleThreads(new ConcurrentHashMap<Integer, Integer> ());
            System.out.println("Synchronized one thread");
            addSingleThread(Collections.synchronizedMap(new HashMap<Integer, Integer> ()));
            System.out.println("Synchronized multiple threads");
            addMultipleThreads(Collections.synchronizedMap(new HashMap<Integer, Integer> ()));
        }   
        executor.shutdown();
    }

    private static void addSingleThread(Map<Integer, Integer> map) {
        long start = System.nanoTime();
        for (int i = 0; i < SIZE; i++) {
            map.put(i, i);
        }
        System.out.println(map.size()); //use the result
        long end = System.nanoTime();
        System.out.println("time with single thread: " + (end - start) / 1000000);
    }

    private static void addMultipleThreads(final Map<Integer, Integer> map) throws Exception {
        List<Runnable> runnables = new ArrayList<> ();
        for (int i = 0; i < THREADS; i++) {
            final int start = i;
            runnables.add(new Runnable() {

                @Override
                public void run() {
                    //Trying to have one runnable by bucket
                    for (int j = start; j < SIZE; j += THREADS) {
                        map.put(j, j);
                    }
                }
            });
        }
        List<Future> futures = new ArrayList<> ();
        long start = System.nanoTime();
        for (Runnable r : runnables) {
            futures.add(executor.submit(r));
        }
        for (Future f : futures) {
            f.get();
        }
        System.out.println(map.size()); //use the result
        long end = System.nanoTime();
        System.out.println("time with multiple threads: " + (end - start) / 1000000);
    }
}
于 2013-02-27T18:19:39.210 回答