Skip to content

Instantly share code, notes, and snippets.

@Gikkman
Last active December 1, 2016 09:14
Show Gist options
  • Select an option

  • Save Gikkman/879a8be3d86be6cc78b453b7b05cbbcd to your computer and use it in GitHub Desktop.

Select an option

Save Gikkman/879a8be3d86be6cc78b453b7b05cbbcd to your computer and use it in GitHub Desktop.
A test I wrote to compare read and write speeds of HashMap and ConcurrentHashMap.
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
public class ReadWriteTimeTest
{
private static long SAFE = 0L;
private static final int MAP_MAX_SIZE = 1000000;
private static final int WRITE_ACTIONS = 1 * 1000 * 1000;
private static final int READ_ACTIONS = 1 * 1000 * 1000;
private static final int THREADS = 10;
public static void main(String... args) throws InterruptedException
{
// Test for different amounts of concurrency; from 1 thread up to THREADS.
// This will show how different collections perform under different circumstances
for (int i = 1; i <= THREADS; i++)
{
Map<Integer, Long> globalCache;
List<Thread> clients;
System.out.println("*********************************************************************************************");
System.out.println("* Test config: MAP_MAX_SIZE=" + MAP_MAX_SIZE + " WRITE_ACTIONS=" + WRITE_ACTIONS + " READ_ACTIONS=" + READ_ACTIONS + " THREADS=" + i);
System.out.println("*********************************************************************************************");
System.out.println();
SAFE = 0;
globalCache = new ConcurrentHashMap<>();
System.out.println(" [ConcurrentHashMap]");
clients = createThreads(i, writeTestRunnable(globalCache));
runThreads("WriteTest", "ConcurrentHashMap", clients);
fillCache(globalCache);
clients = createThreads(i, readTestRunnable(globalCache));
runThreads("ReadTest", "ConcurrentHashMap", clients);
System.out.println();
SAFE = 0;
globalCache = new HashMap<>();
System.out.println(" [HashMap]");
clients = createThreads(i, writeTestRunnable(globalCache));
runThreads("WriteTest", "HashMap", clients);
fillCache(globalCache);
clients = createThreads(i, readTestRunnable(globalCache));
runThreads("ReadTest", "HashMap", clients);
System.out.println();
}
System.out.println("Avoid compiler optimization: " + SAFE);
}
private static Runnable readTestRunnable(final Map<Integer, Long> globalCache)
{
// This runnable simulates a lot of read requests coming in on different threads, reading from the cache.
return new Runnable() {
@Override
public void run()
{
try
{
// Generate random numbers, representing the cache key
Random rng = ThreadLocalRandom.current();
for (int i = 0; i < READ_ACTIONS && !Thread.interrupted(); i++)
{
// This operation is simply to avoid the compiler from optimizing away our method call
SAFE += globalCache.get(rng.nextInt(MAP_MAX_SIZE));
}
}
catch (Exception e)
{
System.out.println("\t" + Thread.currentThread().getName() + " - Read Exception: " + e);
}
}
};
}
private static Runnable writeTestRunnable(final Map<Integer, Long> globalCache)
{
// This runnable simulates a lot of write requests coming in on different threads, writing to the cache.
return new Runnable() {
@Override
public void run()
{
try
{
// If a HashMap is passed to this method, give each thread their own HashMap
final Map<Integer, Long> localCache = globalCache instanceof HashMap ? new HashMap<Integer, Long>() : globalCache;
// Generate random numbers, representing the cache key
Random rng = ThreadLocalRandom.current();
for (int i = 0; i < WRITE_ACTIONS && !Thread.interrupted(); i++)
{
// TODO: Switch 'localCache' to 'globalCache' if you want to see what happens when a lot of
// threads edits the same HashMap concurrently
localCache.put(rng.nextInt(MAP_MAX_SIZE), rng.nextLong());
}
}
catch (Exception e)
{
System.out.println("\t" + Thread.currentThread().getName() + " - Write Exception: " + e);
}
}
};
}
private static void fillCache(Map<Integer, Long> globalCache)
{
// Simply fills the map from index 0 to index MAX_MAP_SIZE, so that there
// aren't any holes when we do the read test
try
{
for (int i = 0; i < MAP_MAX_SIZE; i++)
{
globalCache.put(i, (long) i);
}
}
catch (Exception e)
{
System.out.println("Filling map failed: " + e);
}
}
private static List<Thread> createThreads(int nrThreads, Runnable runnable)
{
List<Thread> list = new LinkedList<>();
for (int i = 0; i < nrThreads; i++)
{
list.add(new Thread(runnable));
}
return list;
}
@SuppressWarnings("deprecation")
private static void runThreads(String testName, String mapType, List<Thread> threads) throws InterruptedException
{
System.out.println(testName + " started");
long start = System.currentTimeMillis();
for (Thread t : threads)
{
t.start();
}
for (Thread t : threads)
{
// In case regular HashMaps writes to the same globalCache map, the threads risks breaking.
// Therefore, I have this code in place to clean them up
t.join(10 * 1000);
t.stop();
}
long stop = System.currentTimeMillis();
System.out.println(testName + " time: " + (stop - start) + "ms [" + mapType + "]");
}
}
@Gikkman

Gikkman commented Dec 1, 2016

Copy link
Copy Markdown
Author

It turned out that having multiple threads writing to different keys in a regular HashMap didn't really work out, thus the somewhat strange solution on row 90.
If you still want to test what can potentially happen when multiple threads write to the same HashMap concurrently, switch row 99 to globalCache.put instead, so that each thread puts data into the same HashMap concurrently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment