Java 基于并发HashMap的Regadring程序

Java 基于并发HashMap的Regadring程序,java,collections,Java,Collections,在一次采访中,interviwer询问了并发哈希映射和it功能,我对此进行了详细解释。他说,在并发HashMap更新操作的情况下,ConcurrentHashMap只锁定映射的一部分,而不是整个映射 所以他让我写一个简单的程序,证明在更新操作期间,ConcurrentHashMap只锁定Map的一部分,而不是整个Map。我无法做到这一点,所以请告诉我如何做到这一点。面试官可能希望得到一个简单的答案,例如: 如果为get/put操作同步了整个map,那么添加线程不会提高吞吐量,因为瓶颈将是同步的块

在一次采访中,interviwer询问了并发哈希映射和it功能,我对此进行了详细解释。他说,在并发HashMap更新操作的情况下,ConcurrentHashMap只锁定映射的一部分,而不是整个映射


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

面试官可能希望得到一个简单的答案,例如:

如果为get/put操作同步了整个map,那么添加线程不会提高吞吐量,因为瓶颈将是同步的块。然后,您可以使用synchronizedMap编写一段代码,显示添加线程没有帮助 因为map使用多个锁,并且假设您的机器上有多个内核,添加线程将提高吞吐量 下面的示例输出以下内容:

同步一个线程: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);
    }
}

面试官可能希望得到一个简单的答案,例如:

如果为get/put操作同步了整个map,那么添加线程不会提高吞吐量,因为瓶颈将是同步的块。然后,您可以使用synchronizedMap编写一段代码,显示添加线程没有帮助 因为map使用多个锁,并且假设您的机器上有多个内核,添加线程将提高吞吐量 下面的示例输出以下内容:

同步一个线程: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);
    }
}

如果整个映射在更新期间被锁定与否,您希望看到什么行为?一个简单的“天真”猜测是,它只是锁定了相关的“bucket”,不管它是如何实现的。顺便说一句,我不相信您可以仅使用公共API来证明这种行为。鉴于内部分区的详细信息尚未指定,这将是一个很难证明的问题。如果整个地图在更新期间被锁定与否,您希望看到什么行为?一个简单的“天真”猜测是,它只是锁定了相关的“bucket”,不管它是如何实现的。顺便说一句,我不相信你可以只用公共API来证明这种行为。考虑到内部分区的细节尚未确定,这将是一个很难证明的问题。非常感谢,完美的解释。请你再次简要解释一下上述程序,我已经部分理解了,请提前再解释一点,谢谢。这两种方法只需在地图上添加100万个条目。第一个运行在主线程中,第二个运行在具有16个线程的执行器服务中。为了帮助CHM,我使用16步进行计数,因此一个线程将放入0、15、31等,第二个线程放入1、16、32等,这将减少锁争用,并将每个线程与一个锁关联。根据密钥模16的哈希代码,有16个锁。请注意,如果线程数超过100个,效果会更明显,同步版本确实会受到影响。非常感谢,完美的解释。请您再次简要解释上述程序,我已经部分理解了,请再解释一次。提前感谢。这两种方法只需在地图上添加100万个条目。第一个运行在主线程中,第二个运行在具有16个线程的执行器服务中。为了帮助CHM,我使用16步进行计数,因此一个线程将放入0、15、31等,第二个线程放入1、16、32等,这将减少锁争用,并将每个线程与一个锁关联。根据密钥模16的哈希代码,有16个锁。请注意,随着threa的增加,效果更加明显 ds有100个线程,同步化版本确实会受到影响。