【问题标题】:Regadring program on concurrent HashMap关于 concurrentHashMap 上的程序
【发布时间】:2013-02-13 15:45:18
【问题描述】:

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

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

【问题讨论】:

  • 如果整个地图在更新期间被锁定,您希望看到什么行为?
  • 一个简单的“天真”猜测是它只是锁定了相关的“存储桶”,但是它已经实现了。
  • 顺便说一句,我不相信您可以仅使用公共 API 证明这种行为。
  • 鉴于内部分区的细节尚未明确,这将是一个难以“证明”的问题。

标签: java collections


【解决方案1】:

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

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

下面的例子输出如下:

同步一个线程:30
同步多线程:96
并发1个线程: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);
    }
}

【讨论】:

  • 非常感谢,完美的解释请您再次简要解释一下上面的程序我已经部分理解了,请再解释一次提前谢谢。
  • 这两种方法只是简单地将 100 万个条目添加到地图中。第一个在主线程中运行,第二个使用具有 16 个线程的执行器服务运行。为了尝试帮助 CHM,我计算了 16 步(因此一个线程将放置 0、15、31 等,第二个线程将放置 1、16、32 等),这应该减少锁争用并将每个线程与一个锁相关联(有 16 个锁,基于密钥模 16 的哈希码)。请注意,线程越多,效果越明显(100 个线程,同步版本确实会受到影响)。
猜你喜欢
  • 2013-07-13
  • 1970-01-01
  • 1970-01-01
  • 2020-10-13
  • 2013-07-16
  • 1970-01-01
  • 2014-04-24
  • 2014-01-28
  • 2018-06-15
相关资源
最近更新 更多