Java 7: HashMap vs ConcurrentHashMap
Join the DZone community and get the full member experience.
Join For Free- Revisit and compare the Java program performance level between the non-thread safe and thread safe Map data structure implementations (HashMap, Hashtable, synchronized HashMap, ConcurrentHashMap)
- Replicate and demonstrate the HashMap infinite looping problem using a simple Java program that everybody can compile, run and understand
- Review the usage of the above Map data structures in a real-life and modern Java EE container implementation such as JBoss AS7
- Sun/Oracle JDK & JRE 1.7 64-bit
- Eclipse Java EE IDE
- Windows Process Explorer (CPU per Java Thread correlation)
- JVM Thread Dump (stuck thread analysis and CPU per Thread correlation)
- Intel(R) Core(TM) i5-2520M CPU @ 2.50Ghz (2 CPU cores, 4 logical cores)
- 8 GB RAM
- Windows 7 64-bit
In order to help us achieve the above goals, a simple Java program was created as per below:
- The main Java program is HashMapInfiniteLoopSimulator.java
- A worker Thread class WorkerThread.java was also created
- Initialize different static Map data structures with initial size of 2
- Assign the chosen Map to the worker threads (you can chose between 4 Map implementations)
- Create a certain number of worker threads (as per the header configuration). 3 worker threads were created for this proof of concept NB_THREADS = 3;
- Each of these worker threads has the same task: lookup and insert a new element in the assigned Map data structure using a random Integer element between 1 – 1 000 000.
- Each worker thread perform this task for a total of 500K iterations
- The overall program performs 50 iterations in order to allow enough ramp up time for the HotSpot JVM
- The concurrent threads context is achieved using the JDK ExecutorService
- Generate concurrency against a shared / static Map data structure
- Use a mix of get() and put() operations in order to attempt to trigger internal locks and / or internal corruption (for the non-thread safe implementation)
- Use a small Map initial size of 2, forcing the internal HashMap to trigger an internal rehash/resize
## Number of worker threads
private static final int NB_THREADS = 3;
## Number of Java program iterations
private static final int NB_TEST_ITERATIONS = 50;
## Map data structure assignment. You can choose between 4 structures
// Plain old HashMap (since JDK 1.2)
nonThreadSafeMap = new HashMap<String, Integer>(2);
// Plain old Hashtable (since JDK 1.0)
threadSafeMap1 = new Hashtable<String, Integer>(2);
// Fully synchronized HashMap
threadSafeMap2 = new HashMap<String, Integer>(2);
threadSafeMap2 = Collections.synchronizedMap(threadSafeMap2);
// ConcurrentHashMap (since JDK 1.5)
threadSafeMap3 = new ConcurrentHashMap<String, Integer>(2);
/*** Assign map at your convenience ****/
assignedMapForTest = threadSafeMap3;
Now find below the source code of our sample program.
#### HashMapInfiniteLoopSimulator.java
package org.ph.javaee.training4;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* HashMapInfiniteLoopSimulator
* @author Pierre-Hugues Charbonneau
*
*/
public class HashMapInfiniteLoopSimulator {
private static final int NB_THREADS = 3;
private static final int NB_TEST_ITERATIONS = 50;
private static Map<String, Integer> assignedMapForTest = null;
private static Map<String, Integer> nonThreadSafeMap = null;
private static Map<String, Integer> threadSafeMap1 = null;
private static Map<String, Integer> threadSafeMap2 = null;
private static Map<String, Integer> threadSafeMap3 = null;
/**
* Main program
* @param args
*/
public static void main(String[] args) {
System.out.println("Infinite Looping HashMap Simulator");
System.out.println("Author: Pierre-Hugues Charbonneau");
System.out.println("http://javaeesupportpatterns.blogspot.com");
for (int i=0; i<NB_TEST_ITERATIONS; i++) {
// Plain old HashMap (since JDK 1.2)
nonThreadSafeMap = new HashMap<String, Integer>(2);
// Plain old Hashtable (since JDK 1.0)
threadSafeMap1 = new Hashtable<String, Integer>(2);
// Fully synchronized HashMap
threadSafeMap2 = new HashMap<String, Integer>(2);
threadSafeMap2 = Collections.synchronizedMap(threadSafeMap2);
// ConcurrentHashMap (since JDK 1.5)
threadSafeMap3 = new ConcurrentHashMap<String, Integer>(2); // ConcurrentHashMap
/*** Assign map at your convenience ****/
assignedMapForTest = threadSafeMap3;
long timeBefore = System.currentTimeMillis();
long timeAfter = 0;
Float totalProcessingTime = null;
ExecutorService executor = Executors.newFixedThreadPool(NB_THREADS);
for (int j = 0; j < NB_THREADS; j++) {
/** Assign the Map at your convenience **/
Runnable worker = new WorkerThread(assignedMapForTest);
executor.execute(worker);
}
// This will make the executor accept no new threads
// and finish all existing threads in the queue
executor.shutdown();
// Wait until all threads are finish
while (!executor.isTerminated()) {
}
timeAfter = System.currentTimeMillis();
totalProcessingTime = new Float( (float) (timeAfter - timeBefore) / (float) 1000);
System.out.println("All threads completed in "+totalProcessingTime+" seconds");
}
}
}
#### WorkerThread.java
package org.ph.javaee.training4;
import java.util.Map;
/**
* WorkerThread
*
* @author Pierre-Hugues Charbonneau
*
*/
public class WorkerThread implements Runnable {
private Map<String, Integer> map = null;
public WorkerThread(Map<String, Integer> assignedMap) {
this.map = assignedMap;
}
@Override
public void run() {
for (int i=0; i<500000; i++) {
// Return 2 integers between 1-1000000 inclusive
Integer newInteger1 = (int) Math.ceil(Math.random() * 1000000);
Integer newInteger2 = (int) Math.ceil(Math.random() * 1000000);
// 1. Attempt to retrieve a random Integer element
Integer retrievedInteger = map.get(String.valueOf(newInteger1));
// 2. Attempt to insert a random Integer element
map.put(String.valueOf(newInteger2), newInteger2);
}
}
}
- Plain old Hashtable (since JDK 1.0)
- Fully synchronized HashMap (via Collections.synchronizedMap())
- ConcurrentHashMap (since JDK 1.5)
Infinite Looping HashMap Simulator
Author: Pierre-Hugues Charbonneau
http://javaeesupportpatterns.blogspot.com
All threads completed in 0.984 seconds
All threads completed in 0.908 seconds
All threads completed in 0.706 seconds
All threads completed in 1.068 seconds
All threads completed in 0.621 seconds
All threads completed in 0.594 seconds
All threads completed in 0.569 seconds
All threads completed in 0.599 seconds
………………
/*** Assign map at your convenience ****/
assignedMapForTest = nonThreadSafeMap;
- No output other than the program header
- Significant CPU increase observed from the system
- At some point the Java program will hang and you will be forced to kill the Java process
..\jdk1.7.0\bin>jstack 272
2012-08-29 14:07:26
Full thread dump Java HotSpot(TM) 64-Bit Server VM (21.0-b17 mixed mode):
"pool-1-thread-3" prio=6 tid=0x0000000006a3c000 nid=0x18a0 runnable [0x0000000007ebe000]
java.lang.Thread.State: RUNNABLE
at java.util.HashMap.put(Unknown Source)
at org.ph.javaee.training4.WorkerThread.run(WorkerThread.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"pool-1-thread-2" prio=6 tid=0x0000000006a3b800 nid=0x6d4 runnable [0x000000000805f000]
java.lang.Thread.State: RUNNABLE
at java.util.HashMap.get(Unknown Source)
at org.ph.javaee.training4.WorkerThread.run(WorkerThread.java:29)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
"pool-1-thread-1" prio=6 tid=0x0000000006a3a800 nid=0x2bc runnable [0x0000000007d9e000]
java.lang.Thread.State: RUNNABLE
at java.util.HashMap.put(Unknown Source)
at org.ph.javaee.training4.WorkerThread.run(WorkerThread.java:32)
at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
at java.lang.Thread.run(Unknown Source)
..............
- Thread name: pool-1-thread-2
- CPU @25.71%
- Task: Worker thread executing a HashMap.get() operation
- Thread name: pool-1-thread-1
- CPU @23.55%
- Task: Worker thread executing a HashMap.put() operation
- Thread name: pool-1-thread-3
- CPU @12.02%
- Task: Worker thread executing a HashMap.put() operation
- Thread name: pool-1-thread-1
- CPU @20.88%
- Task: Main Java program execution
#### HashMap.java get() operation
public V get(Object key) {
if (key == null)
return getForNullKey();
int hash = hash(key.hashCode());
/*** P-H add-on- iteration counter ***/
int iterations = 1;
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
/*** Circular dependency check ***/
Entry<K,V> currentEntry = e;
Entry<K,V> nextEntry = e.next;
Entry<K,V> nextNextEntry = e.next != null?e.next.next:null;
K currentKey = currentEntry.key;
K nextNextKey = nextNextEntry != null?(nextNextEntry.key != null?nextNextEntry.key:null):null;
System.out.println("HashMap.get() #Iterations : "+iterations++);
if (currentKey != null && nextNextKey != null ) {
if (currentKey == nextNextKey || currentKey.equals(nextNextKey))
System.out.println(" ** Circular Dependency detected! ["+currentEntry+"]["+nextEntry+"]"+"]["+nextNextEntry+"]");
}
/***** END ***/
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k)))
return e.value;
}
return null;
}
HashMap.get() #Iterations : 1
HashMap.put() #Iterations : 1
HashMap.put() #Iterations : 1
HashMap.put() #Iterations : 1
HashMap.put() #Iterations : 1
HashMap.resize() in progress...
HashMap.put() #Iterations : 1
HashMap.put() #Iterations : 2
HashMap.resize() in progress...
HashMap.resize() in progress...
HashMap.put() #Iterations : 1
HashMap.put() #Iterations : 2
HashMap.put() #Iterations : 1
HashMap.get() #Iterations : 1
HashMap.get() #Iterations : 1
HashMap.put() #Iterations : 1
HashMap.get() #Iterations : 1
HashMap.get() #Iterations : 1
HashMap.put() #Iterations : 1
HashMap.get() #Iterations : 1
HashMap.put() #Iterations : 1
** Circular Dependency detected! [362565=362565][333326=333326]][362565=362565]
HashMap.put() #Iterations : 2
** Circular Dependency detected! [333326=333326][362565=362565]][333326=333326]
HashMap.put() #Iterations : 1
HashMap.put() #Iterations : 1
HashMap.get() #Iterations : 1
HashMap.put() #Iterations : 1
.............................
HashMap.put() #Iterations : 56823
- Total JBoss AS7.1.2 Java files (August 28, 2012 snapshot): 7302
- Total Java classes using java.util.Hashtable: 72
- Total Java classes using java.util.HashMap: 512
- Total Java classes using synchronized HashMap: 18
- Total Java classes using ConcurrentHashMap: 46
Published at DZone with permission of Pierre - Hugues Charbonneau, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments