【问题标题】:Performance evaluation of synchronized Hashmap vs ConcurrentHashMap in JavaJava 中同步 Hashmap 与 ConcurrentHashMap 的性能评估
【发布时间】:2015-11-25 00:27:53
【问题描述】:

我已经基于这个https://dzone.com/articles/java-7-hashmap-vs 编写了一个小测试来测试哪种方法性能更好

threadSafeMap2 = new HashMap<String, Integer>(2);
threadSafeMap2 = Collections.synchronizedMap(threadSafeMap2);

threadSafeMap3 = new ConcurrentHashMap<String, Integer>(2)

这是我的junit测试:

package com.bm.framework.concurrent;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

class WorkerThread implements Runnable 
{

    static final int WORKER_ITERATION_COUNT = 500000;
    private Map<String, Integer> map = null;

    public WorkerThread(Map<String, Integer> assignedMap) 
    {

          this.map = assignedMap;
    }

    @Override
    public void run() 
    {
          for (int i=0; i < WORKER_ITERATION_COUNT; i++) 
          {

                 // Return 2 integers between 1-1000000 inclusive

                 Integer newInteger1 = (int) Math.ceil(Math.random() * 1000000);

                 Integer newInteger2 = (int) Math.ceil(Math.random() * 1000000);                    

                 // 1. Attempt to retrieve a random Integer element

                 Integer retrievedInteger = map.get(String.valueOf(newInteger1));

                 // 2. Attempt to insert a random Integer element

                 map.put(String.valueOf(newInteger2), newInteger2);                
          }
    }
}



public class BmfThreadPoolTest 
{
    private static final int NB_THREADS = 3;
    private static final int NB_TEST_ITERATIONS = 50;


    private static Map<String, Integer> nonThreadSafeMap = null;
    private static Map<String, Integer> threadSafeMap2 = null;
    private static Map<String, Integer> threadSafeMap3 = null;


    @Test
    public void testMapPerformance() throws InterruptedException 
    {
        // this is a test between two styles we see
        // one is to use Collections.synchronizedMap()
        // and the other one is to use directly ConcurrentHashMap which 
        // one is faster?
        // Plain old HashMap (since JDK 1.2)
        nonThreadSafeMap = new HashMap<String, Integer>(2);

        // Fully synchronized HashMap
        threadSafeMap2 = new HashMap<String, Integer>(2);
        threadSafeMap2 = Collections.synchronizedMap(threadSafeMap2);

        // ConcurrentHashMap (since JDK 1.5)
        threadSafeMap3 = new ConcurrentHashMap<String, Integer>(2);
        System.out.println("ConcurrentHashMap");
        beginTest(threadSafeMap3);
        // the second one is always performing poor no matter whether it is hashmap or concurrenthashmap why????
        System.out.println("Collections.synchronizedMap()");
        beginTest(threadSafeMap2);

    }

    private void beginTest(final Map<String, Integer> assignedMapForTest)
    {
        for (int i=0; i<NB_TEST_ITERATIONS; i++) 
        {
            long timeBefore = System.currentTimeMillis();
            long timeAfter = 0;

            Float totalProcessingTime = null;

            ExecutorService executor = Executors.newFixedThreadPool(NB_THREADS);

            for (int j = 0; j < NB_THREADS; j++) 
            {

                   /** Assign the Map at your convenience **/

                   Runnable worker = new WorkerThread(assignedMapForTest);
                   executor.execute(worker);              

            }

            // This will make the executor accept no new threads

            // and finish all existing threads in the queue

            executor.shutdown();

            // Wait until all threads are finish

            while (!executor.isTerminated()) 
            {

            }

            timeAfter = System.currentTimeMillis();

            totalProcessingTime = new Float( (float) (timeAfter - timeBefore) / (float) 1000);

            System.out.println("All threads completed in "+totalProcessingTime+" seconds");
        }
    }   
}

问题出在两个 beginTest() 调用中,第二个总是执行不好,即,如果我这样运行

beginTest(threadSafeMap3);
beginTest(threadSafeMap2); 

最后一个需要更长的时间才能完成,这表明 ConcurrentHashMap 更快。如果我再次像这样交换订单

beginTest(threadSafeMap2);
beginTest(threadSafeMap3); 

最后一个需要更长的时间才能完成,这表明 ConcurrentHashMap 更慢。 为什么我会根据测试中使用地图的顺序得到相互矛盾的结果?

如果我将其中一个注释掉并在两个单独的运行中运行测试(一个用于同步哈希映射,一个用于 ConcurrentHashMap),那么我总是会得到一致的结果,ConcurrentHashMap 是获胜者。

【问题讨论】:

  • 您一定看到了一些副作用。也许在第一次运行期间建立的垃圾必须在之后收集,这会使第二次运行变慢?
  • 我建议不要测量 executor.shutdown()while ...,因为它不是测试的一部分

标签: java collections hashmap


【解决方案1】:
  1. 您应该使用您的实际使用模式对此进行测试:与程序的其余部分相比,地图的开销很可能可以忽略不计 - 即使不是,使用地图的方式也可能很重要。因此,衡量整个应用程序通常比对单个组件进行微观基准测试更好。
  2. 在微基准测试中,您应该预热被测代码,因此在计时阶段不首先采用代码路径,以确保您不是对解释代码或即时编译本身进行基准测试李>
  3. 您应该将测试分成不同的方法。这将允许垃圾收集器释放第一次测试运行所消耗的内存,因此两个测试以相同的堆大小和垃圾收集开销运行。 (在您的代码中,JVM 可能没有意识到一旦第二个测试开始后第一个映射不再使用......)

背景阅读:How do I write a correct micro-benchmark in Java?

【讨论】:

  • 感谢您的回复。我仍然不确定......使用模式如何影响完全两个独立数据存储(如 hashmap 与 concurrenthashmap)的性能。关于 GC,在现实世界的例子中,是不是会发生多种使用模式的组合,从而难以从数据存储中预测性能?我们如何才能使行为保持一致?
  • 对于绝大多数用例,这些差异并不重要(是的,10 ns 可能是 5 ns 的两倍,但除非您每秒执行数百万次,否则用户不会能够感知差异) - 但是当我们在微基准中分裂头发时,它们会变得非常明显。
【解决方案2】:
猜你喜欢
  • 2010-11-20
  • 2010-11-25
  • 2011-10-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-12
  • 2021-02-22
相关资源
最近更新 更多