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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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

1
2
3
4
5
6
7
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
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

1
com.javacodegeeks.examples.concurrenthashmap.App

should be similar to:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
6 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