Correctly Updating Values in Java ConcurrentHashMap
package study.base.types.map;
import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger;
public class ScoreUpdater implements Runnable {
private AtomicInteger completionCounter;
private ConcurrentHashMap<string integer=""> scoreMap;
public ScoreUpdater(ConcurrentHashMap<string integer=""> map, AtomicInteger counter) {
this.scoreMap = map;
this.completionCounter = counter;
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
synchronized (scoreMap) {
Integer currentValue = scoreMap.get("score");
Integer updatedValue = currentValue + 1;
System.out.println(threadName + " :" + updatedValue);
scoreMap.replace("score", currentValue, updatedValue);
completionCounter.incrementAndGet();
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
int threadCount = 33;
ConcurrentHashMap<string integer=""> scoreMap = new ConcurrentHashMap<>(1, 1);
scoreMap.put("score", 0);
AtomicInteger counter = new AtomicInteger(0);
ScoreUpdater task = new ScoreUpdater(scoreMap, counter);
List<thread> threads = new ArrayList<>();
for (int i = 0; i < threadCount; i++) {
Thread t = new Thread(task, String.valueOf(i));
threads.add(t);
}
for (int i = 0; i < threadCount; i++) {
threads.get(i).start();
}
while (counter.intValue() < threadCount) {
// busy wait
}
System.out.println("Final score: " + scoreMap.get("score"));
}
}
The increment logic retrieves the value, adds one, and puts it back. The result is correct because of the synchronized block. Without synchronization, the result would be unpredictable. However, using synchronized this way defeats the purpose of a concurrent map. A better approach would be: **Original approach:**```
synchronized (scoreMap) {
Integer currentValue = scoreMap.get("score");
Integer updatedValue = currentValue + 1;
scoreMap.replace("score", currentValue, updatedValue);
completionCounter.incrementAndGet();
}
Improved approach:```
scoreMap.replace("score", scoreMap.get("score") + 1);
Note: The simplified code above works for this specific scenario because the replace method has built-in atomicity. Why does this single line work? Let's examine the replace method in ConcurrentHashMap: ```
public V replace(K key, V value) {
if (key == null || value == null)
throw new NullPointerException();
return replaceNode(key, value, null);
}
final V replaceNode(Object key, V value, Object cv) {
int hash = spread(key.hashCode());
for (Node<k>[] tab = table;;) {
Node<k> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0 ||
(f = tabAt(tab, i = (n - 1) & hash)) == null)
break;
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
boolean validated = false;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
validated = true;
for (Node<k> e = f, pred = null;;) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
V ev = e.val;
if (cv == null || cv == ev ||
(ev != null && cv.equals(ev))) {
oldVal = ev;
if (value != null)
e.val = value;
else if (pred != null)
pred.next = e.next;
else
setTabAt(tab, i, e.next);
}
break;
}
pred = e;
if ((e = e.next) == null)
break;
}
}
// TreeBin handling omitted for brevity
}
}
if (validated) {
if (oldVal != null) {
if (value == null)
addCount(-1L, -1);
return oldVal;
}
break;
}
}
}
return null;
}
</k></k></k>
The key takeaway is the synchronized (f) block within the implementasion—ConcurrentHashMap's methods have built-in synchronization for certain operations. When learning concurrency, you don't need to dive deep into every detail. Focus on these fundamentals: - Time-sharing and multi-core parallelism principles - Core concurrency concepts - Software-level concurrency implementations - Thread creation in Java - Choosing appropriate lock granularity - Thread-safe data structures in Java - Proper use of synchronized keyword - Thorough testing Reading source code for every class isn't always necessary—do so when you have time or when a specific need arises.