ConcurrentHashMap

java.util.concurrent.ConcurrentHashMap Example

In this post, we are going to discuss about the class java.util.concurrent.ConcurrentHashMap<K,V> and give you and idea of how you can use it on your own code when building robust multi-threaded applications.

1. ConcurrentHashMap Class

The ConcurrentHashMap class provides a concurrent version of the standard HashMap. This is an improvement on the synchronizedMap functionality provided in the Collections class, because those methods return collections that have more locking than is strictly necessary.

The classic HashMap uses a function (the hash function) to determine which “bucket” it will store the key/pair in. This is where the “hash” part of the class’s name comes from. This suggests a rather straightforward multithreaded generalization; instead of needing to lock the whole structure when making a change, it’s only necessary to lock the bucket that’s being altered.

The ConcurrentHashMap class also implements the ConcurrentMap interface, which contains some new methods to provide truly atomic functionality:

  • putIfAbsent()
  • remove()
  • replace()

As with all improvements, there are still a few trade-offs. The semantics of methods that operate on the entire Map, such as Map.size(), and Map.isEmpty(), have been slightly weakened to reflect the concurrent nature of the collection. Since the result of Map.size() could be out of date by the time it is computed, it is really only an estimate, so Map.size() is allowed to return an approximation instead of an exact count. While at first this may seem disturbing, in reality methods like Map.size() and Map.isEmpty() are far less useful in concurrent environments because these quantities are moving targets. So the requirements for these operations were weakened to enable performance optimizations for the most important operations, primarily Map.get(), Map.put(), Map.containsKey(), and Map.remove().

Because it has so many advantages and so few disadvantages compared to Hashtable or synchronizedMap, replacing synchronized Map implementations with ConcurrentHashMap in most cases results only in better scalability. Only if your application needs to lock the map for exclusive access is ConcurrentHashMap not an appropriate drop-in.

Tip
For a good introduction and to know more about Data Structures, Synchronized Collections and Concurrent Collections, please visit the following link:
CopyOnWriteArrayList Example

2. Executing some code

Truck.java

package com.javacodegeeks.examples.concurrenthashmap.beans;

public class Truck {
	private int plates;
	private boolean inprogress;

	public Truck() {
	}

	public Truck(int plates) {
		this.plates = plates;
	}

	public int getPlates() {
		return plates;
	}

	public void setPlates(int plates) {
		this.plates = plates;
	}

	public boolean isInprogress() {
		return inprogress;
	}

	public void setInprogress(boolean inprogress) {
		this.inprogress = inprogress;
	}

	// It is VERY IMPORTANT to implement hasCode() and equals() on classes
	// that will be "stored" in a HashMap
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + plates;
		result = prime * result + (inprogress ? 1231 : 1237);
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Truck other = (Truck) obj;
		if (plates != other.plates)
			return false;
		if (inprogress != other.inprogress)
			return false;
		return true;
	}

	@Override
	public String toString() {
		return "Truck [plates=" + plates + "]";
	}
}

DistributionCenterTruckQueue.java

package com.javacodegeeks.examples.concurrenthashmap.callables;

import java.util.concurrent.Callable;

import com.javacodegeeks.examples.concurrenthashmap.beans.Truck;
import com.javacodegeeks.examples.concurrenthashmap.service.DistribuitionCenterTruckService;
import com.javacodegeeks.examples.concurrenthashmap.service.IDistribuitionCenterVehicleService;

public class DistributionCenterTruckQueue implements Callable<Truck> {
	public static enum OPERATION { ARRIVAL, DEPARTURE, INPROGRESS }
	
	private IDistribuitionCenterVehicleService<Truck> truckService;
	
	private Truck vehicle;
	private OPERATION operation;
	
	public DistributionCenterTruckQueue() { }
	
	public DistributionCenterTruckQueue(Truck vehicle, OPERATION operation) {
		this.vehicle = vehicle;
		this.operation = operation;
		
		this.truckService = new DistribuitionCenterTruckService();
	}

	@Override
	public Truck call() throws Exception {
		
		switch (this.operation) {
			case ARRIVAL:
				System.out.print("Arriving: ");
				this.truckService.arrivalQueue(this.vehicle);
				break;
			case DEPARTURE:
				System.out.print("Departing: ");
				this.truckService.departureQueue(this.vehicle);
				break;
			case INPROGRESS:
				System.out.print("In Progress: ");
				this.vehicle.setInprogress(this.truckService.unloadInProgress(this.vehicle));
				break;
		}
		
		return this.vehicle;
	}
}

IDistribuitionCenterVehicleService.java

package com.javacodegeeks.examples.concurrenthashmap.service;

public interface IDistribuitionCenterVehicleService<T> {
	public void arrivalQueue(T vehicle);
	public boolean unloadInProgress(T vehicle);
	public void departureQueue(T vehicle);
}

DistribuitionCenterTruckService.java

package com.javacodegeeks.examples.concurrenthashmap.service;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.javacodegeeks.examples.concurrenthashmap.beans.Truck;

public class DistribuitionCenterTruckService implements
		IDistribuitionCenterVehicleService<Truck> {
	// Ensure that ONLY ONE ConcurrentHashMap is used for every thread
	private static final ConcurrentMap<Truck, Long> vehicleQueue = new ConcurrentHashMap();

	@Override
	public void arrivalQueue(Truck vehicle) {
		long currentTime = System.currentTimeMillis();
		DistribuitionCenterTruckService.vehicleQueue.putIfAbsent(vehicle, currentTime);
	}

	@Override
	public boolean unloadInProgress(Truck vehicle) {
		return DistribuitionCenterTruckService.vehicleQueue.get(vehicle) != null;
	}
	
	@Override
	public void departureQueue(Truck vehicle) {
		DistribuitionCenterTruckService.vehicleQueue.remove(vehicle);
	}
}

App.java

package com.javacodegeeks.examples.concurrenthashmap;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.javacodegeeks.examples.concurrenthashmap.beans.Truck;
import com.javacodegeeks.examples.concurrenthashmap.callables.DistributionCenterTruckQueue;

public class App {
	// I tested it with up to 10,000 Trucks (threads) without any problems
	private static final int NUM_OF_TRUCKS = 15;
	private static final Truck[] truckList = new Truck[App.NUM_OF_TRUCKS];
	
	private static int random(int m, int n) {
		return (int) (Math.random() * (n - m + 1)) + m;
	}
	
	public static void main(String[] args) {
		// Create NUM_OF_TRUCKS Trucks
		for (int i = 0;i < App.NUM_OF_TRUCKS;i++) {
			App.truckList[i] = new Truck(App.random(1000, 5000));
		}
		
		// Create NUM_OF_TRUCKS Threads
		ExecutorService executorService = Executors.newFixedThreadPool(App.NUM_OF_TRUCKS);
		// Create NUM_OF_TRUCKS Callables with random operations (ARRIVAL or DEPARTURE)
		DistributionCenterTruckQueue[] distributionCenterTruckQueue = new DistributionCenterTruckQueue[App.NUM_OF_TRUCKS];
		for (int i = 0;i < App.NUM_OF_TRUCKS;i++) {
			distributionCenterTruckQueue[i] = new DistributionCenterTruckQueue(App.truckList[i], DistributionCenterTruckQueue.OPERATION.values()[App.random(0, 1)]);
		}
		// Execute the Callables and get the result of each operation
		for (int i = 0;i < App.NUM_OF_TRUCKS;i++) {
			try {
				App.truckList[i] = executorService.submit(distributionCenterTruckQueue[i]).get();
				System.out.println(App.truckList[i]);
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace(System.err);
			}
		}
		
		// Those trucks that have not been removed (DEPARTURE), are still "in progress" (INPROGRESS)
		for (int i = 0;i < App.NUM_OF_TRUCKS;i++) {
			try {
				distributionCenterTruckQueue[i] = new DistributionCenterTruckQueue(App.truckList[i], DistributionCenterTruckQueue.OPERATION.INPROGRESS);
				Truck truck = executorService.submit(distributionCenterTruckQueue[i]).get();
				System.out.println(truck.isInprogress() ? truck + ": True" : truck + ": False");
			} catch (InterruptedException | ExecutionException e) {
				e.printStackTrace(System.err);
			}
		}
		
		// Don't forget to shutdown the ExecutionService
		executorService.shutdown();
	}
}

Let’s explain the methods used in the previous code

The output of the command

com.javacodegeeks.examples.concurrenthashmap.App

should be similar to:

Arriving: Truck [plates=2518]
Departing: Truck [plates=2304]
Arriving: Truck [plates=1704]
Arriving: Truck [plates=1729]
Departing: Truck [plates=1704]
Departing: Truck [plates=3695]
Arriving: Truck [plates=2899]
Arriving: Truck [plates=2641]
Arriving: Truck [plates=4158]
Arriving: Truck [plates=4422]
Arriving: Truck [plates=4163]
Arriving: Truck [plates=4728]
Departing: Truck [plates=1316]
Departing: Truck [plates=1592]
Arriving: Truck [plates=4792]
In Progress: Truck [plates=2518]: True
In Progress: Truck [plates=2304]: False
In Progress: Truck [plates=1704]: False
In Progress: Truck [plates=1729]: True
In Progress: Truck [plates=1704]: False
In Progress: Truck [plates=3695]: False
In Progress: Truck [plates=2899]: True
In Progress: Truck [plates=2641]: True
In Progress: Truck [plates=4158]: True
In Progress: Truck [plates=4422]: True
In Progress: Truck [plates=4163]: True
In Progress: Truck [plates=4728]: True
In Progress: Truck [plates=1316]: False
In Progress: Truck [plates=1592]: False
In Progress: Truck [plates=4792]: True

3. Download the Eclipse project of this tutorial:

This was an example of how to set use the ConcurrentHashMap Class.

Download
You can download the full source code of this example here : concurrenthashmap.zip

Armando Flores

Armando graduated from from Electronics Engineer in the The Public University Of Puebla (BUAP). He also has a Masters degree in Computer Sciences from CINVESTAV. He has been using the Java language for Web Development for over a decade. He has been involved in a large number of projects focused on "ad-hoc" Web Application based on Java EE and Spring Framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Mark Stewart
Mark Stewart
4 years ago

Great example. This is code that is very similar to what I need to test a StopWatch timing function.

Back to top button