What is the difference between ConcurrentHashMap and Collections.synchronizedMap (Map) in terms of performance?

I am trying to evaluate these concepts using code. This is what I ended up with

    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;
        }
    }

I expect that after running the code, the result ConcurrentHashMapwill be faster, since it allows multiple insertion into the map. For SynchronizedMap, since each thread is waiting for the completion of the previous thread (the card is synchronized), the code will act just as it will work with one thread environment

However, the result does not fully reflect what I expected

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

Why is this?

Update

  • WITH

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

I have a result

Millisec of SynchronizedMap cost: 208
Millisec of ConcurrentHashMap cost: 216

Millisec of SynchronizedMap cost: 255
Millisec of ConcurrentHashMap cost: 196
  • WITH

Map<Integer, Person> chm = new ConcurrentHashMap<Integer, Person>(100000);

I have a result

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

Millisec of SynchronizedMap cost: 203
Millisec of ConcurrentHashMap cost: 200
+4
source share
1 answer

If you are doing tests, you should:

  • (- JIT ),
  • (- ).

, , :

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, - , - 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();
}
+1

Source: https://habr.com/ru/post/1534973/


All Articles