From 5d2abc903178a2a7bc03b214a6316744a1581d08 Mon Sep 17 00:00:00 2001 From: Tian Baoqiang Date: Fri, 27 Jan 2017 21:10:49 +0800 Subject: [PATCH] add concurrentmap tests (#1051) --- .../ConcurrentMapAggregateStatusTest.java | 80 +++++++++ .../ConcurrentMapNullKeyValueTest.java | 160 ++++++++++++++++++ .../ConcurrentMapPerformanceTest.java | 98 +++++++++++ .../ConcurretMapMemoryConsistencyTest.java | 78 +++++++++ 4 files changed, 416 insertions(+) create mode 100644 core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapAggregateStatusTest.java create mode 100644 core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapNullKeyValueTest.java create mode 100644 core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapPerformanceTest.java create mode 100644 core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurretMapMemoryConsistencyTest.java diff --git a/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapAggregateStatusTest.java b/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapAggregateStatusTest.java new file mode 100644 index 0000000000..cb43cad24b --- /dev/null +++ b/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapAggregateStatusTest.java @@ -0,0 +1,80 @@ +package com.baeldung.java.concurrentmap; + +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +public class ConcurrentMapAggregateStatusTest { + + private ExecutorService executorService; + private Map concurrentMap; + private List mapSizes; + private int MAX_SIZE = 100000; + + @Before + public void init() { + executorService = Executors.newFixedThreadPool(2); + concurrentMap = new ConcurrentHashMap<>(); + mapSizes = new ArrayList<>(MAX_SIZE); + } + + @Test + public void givenConcurrentMap_whenSizeWithoutConcurrentUpdates_thenCorrect() throws InterruptedException { + Runnable collectMapSizes = () -> { + for (int i = 0; i < MAX_SIZE; i++) { + concurrentMap.put(String.valueOf(i), i); + mapSizes.add(concurrentMap.size()); + } + }; + Runnable retrieveMapData = () -> { + for (int i = 0; i < MAX_SIZE; i++) { + concurrentMap.get(String.valueOf(i)); + } + }; + executorService.execute(retrieveMapData); + executorService.execute(collectMapSizes); + executorService.shutdown(); + executorService.awaitTermination(1, TimeUnit.MINUTES); + + for (int i = 1; i <= MAX_SIZE; i++) { + assertEquals("map size should be consistently reliable", i, mapSizes + .get(i - 1) + .intValue()); + } + assertEquals(MAX_SIZE, concurrentMap.size()); + } + + @Test + public void givenConcurrentMap_whenUpdatingAndGetSize_thenError() throws InterruptedException { + Runnable collectMapSizes = () -> { + for (int i = 0; i < MAX_SIZE; i++) { + mapSizes.add(concurrentMap.size()); + } + }; + Runnable updateMapData = () -> { + for (int i = 0; i < MAX_SIZE; i++) { + concurrentMap.put(String.valueOf(i), i); + } + }; + executorService.execute(updateMapData); + executorService.execute(collectMapSizes); + executorService.shutdown(); + executorService.awaitTermination(1, TimeUnit.MINUTES); + + assertNotEquals("map size collected with concurrent updates not reliable", MAX_SIZE, mapSizes + .get(MAX_SIZE - 1) + .intValue()); + assertEquals(MAX_SIZE, concurrentMap.size()); + } + +} diff --git a/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapNullKeyValueTest.java b/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapNullKeyValueTest.java new file mode 100644 index 0000000000..62a3d10add --- /dev/null +++ b/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapNullKeyValueTest.java @@ -0,0 +1,160 @@ +package com.baeldung.java.concurrentmap; + +import org.junit.Before; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static org.junit.Assert.assertNull; + +public class ConcurrentMapNullKeyValueTest { + + ConcurrentMap concurrentMap; + + @Before + public void setup() { + concurrentMap = new ConcurrentHashMap<>(); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenGetWithNullKey_thenThrowsNPE() { + concurrentMap.get(null); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenGetOrDefaultWithNullKey_thenThrowsNPE() { + concurrentMap.getOrDefault(null, new Object()); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenPutWithNullKey_thenThrowsNPE() { + concurrentMap.put(null, new Object()); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenPutNullValue_thenThrowsNPE() { + concurrentMap.put("test", null); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMapAndKeyAbsent_whenPutWithNullKey_thenThrowsNPE() { + concurrentMap.putIfAbsent(null, new Object()); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMapAndMapWithNullKey_whenPutNullKeyMap_thenThrowsNPE() { + Map nullKeyMap = new HashMap<>(); + nullKeyMap.put(null, new Object()); + concurrentMap.putAll(nullKeyMap); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMapAndMapWithNullValue_whenPutNullValueMap_thenThrowsNPE() { + Map nullValueMap = new HashMap<>(); + nullValueMap.put("test", null); + concurrentMap.putAll(nullValueMap); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenReplaceNullKeyWithValues_thenThrowsNPE() { + concurrentMap.replace(null, new Object(), new Object()); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenReplaceWithNullNewValue_thenThrowsNPE() { + Object o = new Object(); + concurrentMap.replace("test", o, null); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenReplaceOldNullValue_thenThrowsNPE() { + Object o = new Object(); + concurrentMap.replace("test", null, o); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenReplaceWithNullValue_thenThrowsNPE() { + concurrentMap.replace("test", null); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenReplaceNullKey_thenThrowsNPE() { + concurrentMap.replace(null, "test"); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenReplaceAllMappingNull_thenThrowsNPE() { + concurrentMap.put("test", new Object()); + concurrentMap.replaceAll((s, o) -> null); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenRemoveNullKey_thenThrowsNPE() { + concurrentMap.remove(null); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenRemoveNullKeyWithValue_thenThrowsNPE() { + concurrentMap.remove(null, new Object()); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenMergeNullKeyWithValue_thenThrowsNPE() { + concurrentMap.merge(null, new Object(), (o, o2) -> o2); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenMergeKeyWithNullValue_thenThrowsNPE() { + concurrentMap.put("test", new Object()); + concurrentMap.merge("test", null, (o, o2) -> o2); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMapAndAssumeKeyAbsent_whenComputeWithNullKey_thenThrowsNPE() { + concurrentMap.computeIfAbsent(null, s -> s); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMapAndAssumeKeyPresent_whenComputeWithNullKey_thenThrowsNPE() { + concurrentMap.computeIfPresent(null, (s, o) -> o); + } + + @Test(expected = NullPointerException.class) + public void givenConcurrentHashMap_whenComputeWithNullKey_thenThrowsNPE() { + concurrentMap.compute(null, (s, o) -> o); + } + + @Test + public void givenConcurrentHashMap_whenMergeKeyRemappingNull_thenRemovesMapping() { + Object oldValue = new Object(); + concurrentMap.put("test", oldValue); + concurrentMap.merge("test", new Object(), (o, o2) -> null); + assertNull(concurrentMap.get("test")); + } + + @Test + public void givenConcurrentHashMapAndKeyAbsent_whenComputeWithKeyRemappingNull_thenRemainsAbsent() { + concurrentMap.computeIfPresent("test", (s, o) -> null); + assertNull(concurrentMap.get("test")); + } + + @Test + public void givenKeyPresent_whenComputeIfPresentRemappingNull_thenMappingRemoved() { + Object oldValue = new Object(); + concurrentMap.put("test", oldValue); + concurrentMap.computeIfPresent("test", (s, o) -> null); + assertNull(concurrentMap.get("test")); + } + + @Test + public void givenKeyPresent_whenComputeRemappingNull_thenMappingRemoved() { + Object oldValue = new Object(); + concurrentMap.put("test", oldValue); + concurrentMap.compute("test", (s, o) -> null); + assertNull(concurrentMap.get("test")); + } + +} diff --git a/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapPerformanceTest.java b/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapPerformanceTest.java new file mode 100644 index 0000000000..3275a47b90 --- /dev/null +++ b/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurrentMapPerformanceTest.java @@ -0,0 +1,98 @@ +package com.baeldung.java.concurrentmap; + +import org.junit.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.concurrent.*; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +public class ConcurrentMapPerformanceTest { + + @Test + public void givenMaps_whenGetPut500KTimes_thenConcurrentMapFaster() throws Exception { + Map hashtable = new Hashtable<>(); + Map synchronizedHashMap = Collections.synchronizedMap(new HashMap<>()); + Map concurrentHashMap = new ConcurrentHashMap<>(); + + long hashtableAvgRuntime = timeElapseForGetPut(hashtable); + long syncHashMapAvgRuntime = timeElapseForGetPut(synchronizedHashMap); + long concurrentHashMapAvgRuntime = timeElapseForGetPut(concurrentHashMap); + + assertTrue(hashtableAvgRuntime > concurrentHashMapAvgRuntime); + assertTrue(syncHashMapAvgRuntime > concurrentHashMapAvgRuntime); + + System.out.println(String.format("Hashtable: %s, syncHashMap: %s, ConcurrentHashMap: %s", hashtableAvgRuntime, syncHashMapAvgRuntime, concurrentHashMapAvgRuntime)); + + } + + private long timeElapseForGetPut(Map map) throws InterruptedException { + ExecutorService executorService = Executors.newFixedThreadPool(4); + long startTime = System.nanoTime(); + for (int i = 0; i < 4; i++) { + executorService.execute(() -> { + for (int j = 0; j < 500_000; j++) { + int value = ThreadLocalRandom + .current() + .nextInt(10000); + String key = String.valueOf(value); + map.put(key, value); + map.get(key); + } + }); + } + executorService.shutdown(); + executorService.awaitTermination(1, TimeUnit.MINUTES); + return (System.nanoTime() - startTime) / 500_000; + } + + @Test + public void givenConcurrentMap_whenKeyWithSameHashCode_thenPerformanceDegrades() throws InterruptedException { + class SameHash { + @Override + public int hashCode() { + return 1; + } + } + int executeTimes = 5000; + + Map mapOfSameHash = new ConcurrentHashMap<>(); + ExecutorService executorService = Executors.newFixedThreadPool(2); + long sameHashStartTime = System.currentTimeMillis(); + for (int i = 0; i < 2; i++) { + executorService.execute(() -> { + for (int j = 0; j < executeTimes; j++) { + mapOfSameHash.put(new SameHash(), 1); + } + }); + } + executorService.shutdown(); + executorService.awaitTermination(5, TimeUnit.SECONDS); + + long mapOfSameHashDuration = System.currentTimeMillis() - sameHashStartTime; + Map mapOfDefaultHash = new ConcurrentHashMap<>(); + executorService = Executors.newFixedThreadPool(2); + long defaultHashStartTime = System.currentTimeMillis(); + for (int i = 0; i < 2; i++) { + executorService.execute(() -> { + for (int j = 0; j < executeTimes; j++) { + mapOfDefaultHash.put(new Object(), 1); + } + }); + } + executorService.shutdown(); + executorService.awaitTermination(5, TimeUnit.SECONDS); + + long mapOfDefaultHashDuration = System.currentTimeMillis() - defaultHashStartTime; + assertEquals(executeTimes * 2, mapOfDefaultHash.size()); + assertNotEquals(executeTimes * 2, mapOfSameHash.size()); + System.out.println(String.format("same-hash: %s, default-hash: %s", mapOfSameHashDuration, mapOfDefaultHashDuration)); + assertTrue("same hashCode() should greatly degrade performance", mapOfSameHashDuration > mapOfDefaultHashDuration * 10); + } + +} diff --git a/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurretMapMemoryConsistencyTest.java b/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurretMapMemoryConsistencyTest.java new file mode 100644 index 0000000000..9a6359f187 --- /dev/null +++ b/core-java/src/test/java/com/baeldung/java/concurrentmap/ConcurretMapMemoryConsistencyTest.java @@ -0,0 +1,78 @@ +package com.baeldung.java.concurrentmap; + +import org.junit.Test; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; + +public class ConcurretMapMemoryConsistencyTest { + + @Test + public void givenConcurrentMap_whenSumParallel_thenCorrect() throws Exception { + Map map = new ConcurrentHashMap<>(); + List sumList = parallelSum100(map, 1000); + assertEquals(1, sumList + .stream() + .distinct() + .count()); + long wrongResultCount = sumList + .stream() + .filter(num -> num != 100) + .count(); + assertEquals(0, wrongResultCount); + } + + @Test + public void givenHashtable_whenSumParallel_thenCorrect() throws Exception { + Map map = new Hashtable<>(); + List sumList = parallelSum100(map, 1000); + assertEquals(1, sumList + .stream() + .distinct() + .count()); + long wrongResultCount = sumList + .stream() + .filter(num -> num != 100) + .count(); + assertEquals(0, wrongResultCount); + } + + @Test + public void givenHashMap_whenSumParallel_thenError() throws Exception { + Map map = new HashMap<>(); + List sumList = parallelSum100(map, 100); + assertNotEquals(1, sumList + .stream() + .distinct() + .count()); + long wrongResultCount = sumList + .stream() + .filter(num -> num != 100) + .count(); + assertTrue(wrongResultCount > 0); + } + + private List parallelSum100(Map map, int executionTimes) throws InterruptedException { + List sumList = new ArrayList<>(1000); + for (int i = 0; i < executionTimes; i++) { + map.put("test", 0); + ExecutorService executorService = Executors.newFixedThreadPool(4); + for (int j = 0; j < 10; j++) { + executorService.execute(() -> { + for (int k = 0; k < 10; k++) + map.computeIfPresent("test", (key, value) -> value + 1); + }); + } + executorService.shutdown(); + executorService.awaitTermination(5, TimeUnit.SECONDS); + sumList.add(map.get("test")); + } + return sumList; + } + +}