1. Java Thread-safe Map Overview

The Map object is an associative containers that store elements, formed by a combination of a uniquely identify key and a mapped value. In Java, the most important Map implementation is HashMap, unfortunately it is not synchronized.

Tips: A map cannot contain duplicate keys, each key can map to at most one value.

What does the thread safe Map means? If multiple threads access a hash map concurrently, and at least one of the threads modifies the map structurally, it must be synchronized externally to avoid an inconsistent view of the contents. (A structural modification is any operation that adds or deletes one or more mappings; merely changing the value associated with a key that an instance already contains is not a structural modification.)

2. How to make Map Thread-safe?

2.1 Use Plain old Hashtable

Hashtable is the part of JDK 1.0. It provided an easy-to-use, thread-safe, associative map capability, and it was certainly convenient but NOT SO efficient. why it is not? the cost Hashtable pays for synchronization is that any threads before entering the method to perform an update on a hashtable will have to immediately acquire a lock on the object while others will wait for lock to be released.

As we know it is a generic synchronized solution of hashmap and their are roughly equivalent, but still there have few differences, one of them is that HashMap allows null values as key and value whereas Hashtable doesn’t allow nulls.

2.2 Use  the synchronized collections wrappers - synchronizedMap

By using synchronizedMap to warp the map when creating, it returns a warped synchronized (thread-safe) map backed by the specified map. In order to guarantee serial access, it is critical that all access to the backing map is accomplished through the returned map.

2.3 Use ConcurrentHashMap in Java concurrent package

ConcurrentHashMap was introduced since JDK1.5, which is an alternative of Hashtable and offset the deficiency on scalability and performance, internally ConcurrentHashMap allows concurrent threads to read values without locking at all, except in a minority of cases, the current thread in multiple environment thread to update values while only acquiring locks over localised segments of the internal data structure.

The flowing code snippet is example code, you can refer to how to instantiate the map and put key and value into it.

//Hashtable Example Code
Map<String, Integer> threadSafeMap = new Hashtable<String, Integer>();
//synchronizedMap Example Code.
threadSafeMap = Collections.synchronizedMap(new HashMap<String, Integer>());
//ConcurrentHashMap Example Code
threadSafeMap = new ConcurrentHashMap<String, Integer>();
threadSafeMap .put("Key1", 123)

3. Current Popularity:

The below finger displays an overall statistics comparison they are currently using in code(this was came my study from partial of current open source and private projects).

thread-safe-map-usage-percentage

4. Thread-safe Hash Map performance benchmark Testing

4.1 Prerequisites and environment

The following local PC was used for the problem replication process and performance measurements:

Computer: Intel(R) Xeon(R)CPU X5670 @2.93GHz 3.10 GHz, 7.58 GB of RAM
System: Microsoft Windows Server 2003 X64 Service Pack 2
Development Tools: Eclipse Indigo Service Release 1, JDK1.7.0_13

4.2 Code implementation

I created the below program to test the three synchronized Hash Map implementation. The code is fairly simple, but with all what need to be done. The main Java program is ThreadSafeMapTesting.java:

  1. Initialize a static map instance and assign a thread-safe Map data structures, this can be neither hastable, wrapped hashmap or ConcurrentHashMap, we will test them separately and drag the performance figure for each ones.
  2. Create certain threads and each thread lookup the map by a key and insert a random integer to it for 100000 times. By increasing the concurrent threads, we will see how it performs form a normal to extremely condition.
  3. Loop 10 times the same action in order to reduce inaccuracy and print the each used time in MS and also calculate the average time.
import java.util.Map;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Random;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ThreadSafeMapTesting {

	public final static int THREADS = 3;

	public static Map<String, Integer> threadSafeMap = null;
	public static long averageTime = 0;

	public static void main(String[] args) throws InterruptedException {
		for (int i = 0; i < 10; i++) {
			threadSafeMap = new Hashtable<String, Integer>();
			// threadSafeMap = Collections
			// .synchronizedMap(new HashMap<String, Integer>());
			// threadSafeMap = new ConcurrentHashMap<String, Integer>();

			long time = System.nanoTime();
			ExecutorService service = Executors.newFixedThreadPool(THREADS);

			for (int j = 0; j < THREADS; j++) {
				service.execute(new Runnable() {
					@Override
					public void run() {

						for (int i = 0; i < 100000; i++) {
							// Test to get and insert a random 
							//Integer element.
							Integer num = (int) Math.ceil(
									Math.random() * 100000);
							Integer value = threadSafeMap.get(String
									.valueOf(num));
							threadSafeMap.put(String.valueOf(num), num);
						}
					}
				});
			}

			// Make sure the executor accept no new threads.
			service.shutdown();
			service.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
			long timeUsed = (System.nanoTime() - time) / 1000000L;
			averageTime += timeUsed;
			System.out.println("All threads are completed in " 
			+ timeUsed + " ms");
		}
		System.out.println("The average time is " + averageTime / 10 + " ms");
	}
}

Output when I executed this program for one case.

All threads are completed in 301 ms
All threads are completed in 448 ms
All threads are completed in 430 ms
All threads are completed in 363 ms
All threads are completed in 282 ms
All threads are completed in 291 ms
All threads are completed in 343 ms
All threads are completed in 290 ms
All  threads are completed in 308 ms
All threads are completed in 294 ms
The average time is 335 ms

4.3 Testing performance benchmark Result.

By changing the static variable THREADS to 1, 3, 5, 10, 20 and 50 concurrent threads and set different assignee of  synchronized hashmap we approached. we got the the below testing output.
thread-safe map performance benchmark

Concurrent Threads Collections.synchronizedMap Hashtable ConcurrentHashMap
1 Thread 0.128 0.102 0.103
3 Threads 0.462 0.408 0.23
5 Threads 0.737 0.687 0.344
10 Threads 1.489 1.366 0.694
20 Threads 3.295 2.741 1.36
50 Threads 6.179 6.877 3.041

While reading this table, we can conclude that their performance levels are quite close in a single-threaded case. (the Hashmap was not added, but definitely it is the best one whereas it provides no concurrency protection). In multiple threads case, the testing result reveals that ConcurrentHashMap provides better scalability than Hashtable and synchronizedMap, almost twice better performance.

The testing result only for Collections.synchronizedMap.
thread-safe-map-1
The testing result only for Hashtable.
thread-safe-map-2
The testing result only for ConcurrentHashMap.
thread-safe-map-3

5. Conclusion

ConcurrentHashMap is a very useful class for many concurrent applications.  it’s also an impressive feat of coding. I believe after reading this article, the action you may take is if are willing to live with the restrictions that must be thread safety implies, it’s the time to change to ConcurrentHashMap. if you don’t have this expectations keep what was.