e-Zest members share technology ideas to foster digital transformation.

Writing Thread-Safe Programs Using ConcurrentHashMap

Written by Madhura Oak | May 2, 2012 2:32:58 PM

When your program can be accessed by multiple threads concurrently, you need to ensure that it is thread-safe. What is thread safety? A thread-safe program behaves correctly and is always in a consistent state when accessed by multiple threads regardless of the interleaving of the thread execution. A thread-safe program runs as consistently as if it is run by a single thread.

Stateless objects which do not share any state between the method calls are always thread-safe. However, when the state is shared across multiple methods of a class and multiple threads can call these methods concurrently then you need to take explicit measures to ensure thread safety.

In this blog, I am writing about implementing thread safety using ConcurrentHashMap.

Java SE 5 introduced ConcurrentHashMap – a Map implementation which provides better concurrency and scalability as compared to Hashtable.

The synchronized methods of Hashtable obtain lock on the entire hashtable object during any retrieval or update operations. This can lead to very long response time under heavy load. Since the underlying data is stored in a series of buckets in a hash map (a hashtable is a hash map implementation), it is more efficient to lock only a bucket while accessing a map instead of obtaining lock on the entire map. This mechanism is called as lock striping. ConcurrentHashMap uses this finer-grained locking with which it provides 16 locks by default and reduces lock contention when multiple threads access a ConcurrentHashMap object concurrently.

The Collections.synchronizedMap(map) provides a blocking map for concurrent operations whereas ConcurrentHashMap blocks only writes to the map. The performance of ConcurrentHashMap is better than Collections.synchronizedMap(map).

Background

We had a Java EE application which used servlets to interact with a database on a remote tier. One such servlet searched a program by program number from the “Program” table every time the servlet was called. There were a few thousand programs in the database and they were never modified. Also, few of them were frequently searched. So we decided to scale up the performance of the servlet by implementing a cache which could save a few programs searched frequently, in it. In this way, the servlet would first try to fetch the program in the cache and if not found in the cache then would go and fetch it from the database. This would reduce the calls made to the remote database.

ConcurrentHashMap

We decided to use a ConcurrentHashMap<String,Program> as a cache which could fetch a program by programNumber and it could be shared by multiple request threads.

The outline of the program is given in Listing 1.

public class SearchProgramByNumberServlet extends HttpServlet {
      private ConcurrentMap programsCache =
                  new ConcurrentHashMap();

      protected void doPost(HttpServletRequest request,
                  HttpServletResponse response)
                  throws ServletException, IOException {

         /* Get the program number entered as input. Fetch program
         by program number from the cache. If not found in the cache
         fetch it from the database. If found in database put it in
         cache. If program number is empty string or is in invalid
         format or the program is not found in database then set
         proper error messages. */

      }
}

Listing 1. Outline of the program

This servlet is called from a JSP page which displays a form to enter program number to be searched as input. The code to get the programNumber and validate it is given in Listing 2.

private static final String PROGRAM_NUMBER = "programNumber";
private static final String PROGRAM_NUMBER_FORMAT =
		"[a-zA-Z]{2}\d{4}";
private static final String PROGRAM = "program";
private static final String ERROR = "error";
private static final String FAILURE_VIEW = "/searchProgram.jsp";
private static final String SUCCESS_VIEW = "/program.jsp";

protected void doPost(…){
	String view = null;
	String programNumber = request.getParameter(PROGRAM_NUMBER);
	Program program = null;
	if(programNumber != null &amp;&amp; programNumber.trim().length() != 0) {
		//program number is not an empty string
		//check the format of input
		if(programNumber.matches(PROGRAM_NUMBER_FORMAT)) {

                /* Get the program from the cache. If not found in cache
                   fetch from database. If the program is found in the
                   database add it in the cache. If the program is not
                   found in database set the error message. */
		}
		else {
			request.setAttribute(ERROR,
                              "Program Number format is invalid");
			view = FAILURE_VIEW;
		}
	}
	else {
		request.setAttribute(ERROR, "Enter Program Number");
		view = FAILURE_VIEW;
	}
	request.getRequestDispatcher(view).forward(request, response);
}

Listing 2. Code to get program number entered in the JSP form and validate it

The code to fetch the program from cache and if not found fetching it from the database is given in Listing 3. The ProgramDAO class is a Data Access Object which also implements Singleton design pattern. The ProgramDAO.getInstance() method returns the singleton instance. The findByProgramNumber() throws checked exception EntityNotFoundException if the program is not found in the database.

program = programsCache.get(programNumber);
if(program == null) {
      try {
            //program is not found in cache. Fetch it in the database
            program = ProgramDAO.getInstance().
                    findByProgramNumber(programNumber);
            programsCache.putIfAbsent(programNumber,program);
            request.setAttribute(PROGRAM, program);
            view = SUCCESS_VIEW;
      }
      catch(EntityNotFoundException exp) {
            //program is not found in the database
            request.setAttribute(ERROR, "Program does not exist");
            view = FAILURE_VIEW;
      }
}

Listing 3. Fetching the program from cache and if not found fetching it from database

The ConcurrentHashMap provides a method putIfAbsent(key, value) which adds the value in the map if no value for the given key is present. This method performs the put-if-absent operation as an atomic operation and hence there is no need of using synchronization on the implementing code.

The programCache used in the above servlet can grow upto the size of number of records in database table. In the next blog, I will discuss about the strategy we used to remove the programs from the cache to implement a fixed size cache.

Other thread-safe and atomic methods of ConcurrentHashMap

  1. The replace(key, value) replaces entry for key only if present in the ConcurrentHashMap.
  2. The replace(key, oldValue, newValue) replaces the entry for key with the new value only if it has the matching old value.
  3. The remove(key, value) removes entry for key only if it has the matching value.

Iterating over ConcurrentHashMap

It is possible to iterate through a ConcurrentHashMap without locking and the iterators are not fail-fast which means that there is no possiblity of ConcurrentModificationException. While a thread is iterating a ConcurrentHashMap other threads can access it. However, after the iterator is created, there is no guarantee that the iterating thread will immediately see all elements added by other threads to the ConcurrentHashMap during iteration. Only the removal and update operations by threads are guaranteed to be reflected. It is also guaranteed that no element will be returned more than once during iteration.

If the threads need to get the additions immediately then the ConcurrentHashMap can be locked using synchronized block or Collections.synchronizedMap() can be used. Locking a collection during the entire iteration is generally considered as a bad practice.

Defining concurrency level on ConcurrentHashMap

When the approximate number of threads concurrently updating a ConcurrentHashMap is known, it is a good idea to use the constructor which allows defining concurrencyLevel for instantiation. The default concurrencyLevel is 16. However, using a very high value can lead to wastage of memory and the longer execution time. Using a very low value can lead to thread contention. When only one thread is updating the map and others are only reading it, it is recommended to use the concurrencyLevel of 1.

Better Locking in Java SE 7

The ConcurrentHashMap in Java SE 7 uses lazy segment initialization. This reduces the memory wastage that occured with the default constructor. An empty map has only 1 segment.

Replacing Hashtable with ConcurrentHashMap

If you are refactoring your multi-threaded application, do consider replacing Hashtable with ConcurrentHashMap as it provides better performance.