在性能方面,ConcurrentHashMap和Collections.synchronizedMap(Map)之间的区别是什么?

时间:2014-04-04 05:53:49

标签: java multithreading concurrency

我试图通过使用代码来评估这些概念。这就是我最终的结果

    public void runCWith3Threads() {

        // mesure add with 3 threads
        for (int i = 0; i < 10; i++) {
            Map<Integer, Person> shm = Collections.synchronizedMap(new HashMap<Integer, Person>());
            Map<Integer, Person> chm = new ConcurrentHashMap<Integer, Person>();

            MapThread sm1 = new MapThread(shm, 0, 20000, "sm1");
            MapThread sm2 = new MapThread(shm, 20000, 30000, "sm2");
            MapThread sm3 = new MapThread(shm, 30000, 50000, "sm3");

            sm1.start();sm2.start();sm3.start();

            while (true) {
                try {
                    sm1.join();
                    sm2.join();
                    sm3.join();
                    break;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            long secondMax = sm1.time > sm2.time ? sm1.time : sm2.time;
            long firstMax = secondMax > sm3.time ? secondMax : sm3.time;

            System.out.println("Millisec of SynchronizedMap cost: " + firstMax);

            MapThread m1 = new MapThread(chm, 0, 20000, "m1");
            MapThread m2 = new MapThread(chm, 20000, 30000, "m2");
            MapThread m3 = new MapThread(chm, 30000, 50000, "m3");

            m1.start();m2.start();m3.start();

            while (true) {
                try {
                    m1.join();
                    m2.join();
                    m3.join();
                    break;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            secondMax = m1.time > m2.time ? m1.time : m2.time;
            firstMax = secondMax > m3.time ? secondMax : m3.time;

            System.out.println("Millisec of ConcurrentHashMap cost: " + firstMax);
            System.out.println();
        }
    }

    public class MapThread extends Thread {
        Map<Integer, Person> map;
        int from;
        int to;
        long time;
        String name;

        public MapThread(Map<Integer, Person> map, int from, int to, String name) {
            this.map = map;
            this.from = from;
            this.to = to;
            this.name = name;
        }

        public void run() {

            long start = System.currentTimeMillis();
            for (int i = from; i < to; i++) {
                map.put(i, new Person());
            }
            long end = System.currentTimeMillis();
            time = end - start;
            return;
        }
    }

我期望的是,在代码运行后,ConcurrentHashMap的结果会更快,因为它允许多次插入地图。对于SynchronizedMap,由于每个线程都在等待前一个线程完成(映射已同步),因此一旦运行单个线程环境,代码将起到相同的作用

然而,结果并没有完全反映出我的预期

Millisec of SynchronizedMap cost: 250
Millisec of ConcurrentHashMap cost: 203

Millisec of SynchronizedMap cost: 171
Millisec of ConcurrentHashMap cost: 172

Millisec of SynchronizedMap cost: 172
Millisec of ConcurrentHashMap cost: 188

Millisec of SynchronizedMap cost: 171
Millisec of ConcurrentHashMap cost: 172

Millisec of SynchronizedMap cost: 187
Millisec of ConcurrentHashMap cost: 172

Millisec of SynchronizedMap cost: 171
Millisec of ConcurrentHashMap cost: 189

Millisec of SynchronizedMap cost: 187
Millisec of ConcurrentHashMap cost: 171

Millisec of SynchronizedMap cost: 188
Millisec of ConcurrentHashMap cost: 171

Millisec of SynchronizedMap cost: 172
Millisec of ConcurrentHashMap cost: 172

Millisec of SynchronizedMap cost: 171
Millisec of ConcurrentHashMap cost: 188

为什么?

更新

  1. Map<Integer, Person> chm = new ConcurrentHashMap<Integer, Person>(100000, 10, 3);

    我有结果

    Millisec of SynchronizedMap cost: 208
    Millisec of ConcurrentHashMap cost: 216
    
    Millisec of SynchronizedMap cost: 255
    Millisec of ConcurrentHashMap cost: 196
    
    1. 使用
    2. Map<Integer, Person> chm = new ConcurrentHashMap<Integer, Person>(100000);

      我有结果

      Millisec of SynchronizedMap cost: 204
      Millisec of ConcurrentHashMap cost: 283
      
      Millisec of SynchronizedMap cost: 203
      Millisec of ConcurrentHashMap cost: 200
      

1 个答案:

答案 0 :(得分:1)

如果你正在做基准测试,你应该:

  1. 做一个预热阶段(由于JIT编译和类加载),
  2. 多次重复测试(由于垃圾回收)。
  3. 如果我创建类似于您的基准测试的基准测试,我会得到以下结果:

    Warmup...
    Benchmark...
     4 *  500000: 0.22s / 0.04s
     4 * 1000000: 0.55s / 0.10s
     4 * 1500000: 1.10s / 0.16s
     4 * 2000000: 0.90s / 0.19s
     4 * 2500000: 1.68s / 0.25s
    

    第一个数字表示线程数,第二个数字表示int范围的大小,第三个数字表示 synchronized Map 的持续时间,第四个数字表示 ConcurrentHashMap的持续时间。如您所见, ConcurrentHashMap 在所有情况下都要快得多。

    您可以在下面找到整个Java代码。请注意,它使用了Java 8中的功能。但是,这应该对结果没有影响:

    public static void main(String... args) {
        System.out.println("Warmup...");
        for (int i = 0; i < 10000; ++i) {
            test(Collections.synchronizedMap(new HashMap<>()), 2, 1000);
            test(new ConcurrentHashMap<>(), 2, 1000);
        }
        System.out.println("Benchmark...");
        for (int i = 0; i < 5; ++i) {
            int threads = 4;
            int range = 500000 * (i + 1);
            System.out.printf("%2d * %7d: %s / %s\n",
                threads, range,
                test(Collections.synchronizedMap(new HashMap<>()), threads, range),
                test(new ConcurrentHashMap<>(), threads, range));
        }
    }
    public static String test(Map<Integer,Object> map, int threads, int range) {
        long duration = IntStream.range(0, 10)
            .mapToLong(i -> execute(
                    IntStream.range(0, threads)
                    .<Runnable>mapToObj(t -> () -> bulkPut(map, t * range, (t + 1) * range, new Object()))
                    .toArray(Runnable[]::new)))
            .min().getAsLong();
        return String.format("%4.2fs",
            duration / 1000.0, threads, range);
    }
    public static <T> void bulkPut(Map<Integer,T> map, int from, int to, T value) {
        for (int i = from; i < to; ++i) {
            map.put(i, value);
        }
    }
    public static long execute(Runnable... runnables) {
        List<Thread> threads = new ArrayList<>();
        AtomicLong duration = new AtomicLong();
        for (Runnable runnable : runnables) {
            Thread thread = new Thread(() -> {
                    long start = System.currentTimeMillis();
                    try {
                        runnable.run();
                    } finally {
                        long elapsed = System.currentTimeMillis() - start;
                        duration.accumulateAndGet(elapsed, Math::max);
                    }
                });
            thread.start();
            threads.add(thread);
        }
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        return duration.get();
    }