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.
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
public V putIfAbsent(K key, V value)
– If the specified key is not already associated with a value, associate it with the given value.public V get(Object key)
– Returns the value to which the specified key is mapped, or null if this map contains no mapping for the key.public V remove(Object key)
– Removes the key (and its corresponding value) from this map. This method does nothing if the key is not in the map.
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.
You can download the full source code of this example here : concurrenthashmap.zip
Great example. This is code that is very similar to what I need to test a StopWatch timing function.