diff --git a/pdb-api/.gitignore b/pdb-api/.gitignore index 080ea93..6b45b68 100644 --- a/pdb-api/.gitignore +++ b/pdb-api/.gitignore @@ -3,3 +3,4 @@ /.settings/ /.classpath /.project +/test-output/ diff --git a/pdb-api/src/main/java/org/lucares/pdb/api/UniqueStringIntegerPairs.java b/pdb-api/src/main/java/org/lucares/pdb/api/UniqueStringIntegerPairs.java index dfc5924..ff987a9 100644 --- a/pdb-api/src/main/java/org/lucares/pdb/api/UniqueStringIntegerPairs.java +++ b/pdb-api/src/main/java/org/lucares/pdb/api/UniqueStringIntegerPairs.java @@ -121,8 +121,12 @@ public class UniqueStringIntegerPairs { public Integer computeIfAbsent(final String first, final Function mappingFunction) { if (!stringToInt.containsKey(first)) { - final Integer second = mappingFunction.apply(first); - put(first, second); + synchronized (stringToInt) { + if (!stringToInt.containsKey(first)) { + final Integer second = mappingFunction.apply(first); + put(first, second); + } + } } return stringToInt.get(first); diff --git a/pdb-api/src/test/java/org/lucares/pdb/api/StringCompressorTest.java b/pdb-api/src/test/java/org/lucares/pdb/api/StringCompressorTest.java index 7952b1b..e2b506c 100644 --- a/pdb-api/src/test/java/org/lucares/pdb/api/StringCompressorTest.java +++ b/pdb-api/src/test/java/org/lucares/pdb/api/StringCompressorTest.java @@ -3,6 +3,15 @@ package org.lucares.pdb.api; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; import org.lucares.utils.file.FileUtils; import org.testng.Assert; @@ -49,4 +58,49 @@ public class StringCompressorTest { } } + + private static final class StringInserter implements Callable> { + + private final StringCompressor stringCompressor; + private final int numEntries; + + public StringInserter(final StringCompressor stringCompressor, final int numEntries) { + this.stringCompressor = stringCompressor; + this.numEntries = numEntries; + } + + @Override + public List call() throws Exception { + + final List result = new ArrayList<>(); + for (int i = 0; i < numEntries; i++) { + final String s = UUID.randomUUID().toString(); + stringCompressor.put(s); + result.add(s); + } + return result; + } + }; + + @Test(invocationCount = 1) + public void testPutConcurrently() throws InterruptedException, ExecutionException { + final UniqueStringIntegerPairs usip = new UniqueStringIntegerPairs(); + final StringCompressor stringCompressor = new StringCompressor(usip); + + final ExecutorService pool = Executors.newCachedThreadPool(); + + final int numEntries = 1000; + final Future> future1 = pool.submit(new StringInserter(stringCompressor, numEntries)); + final Future> future2 = pool.submit(new StringInserter(stringCompressor, numEntries)); + final Future> future3 = pool.submit(new StringInserter(stringCompressor, numEntries)); + + future1.get(); + future2.get(); + future3.get(); + + pool.shutdown(); + pool.awaitTermination(1, TimeUnit.MILLISECONDS); + + Assert.assertEquals((int) usip.getHighestInteger(), 3 * numEntries - 1); + } }