Given the ConcurrentHashMap javadocs state:
"Iterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator"
I think I am guarranteed that in the example below one or both threads will call fireAllFinished(). Can there ever be a case where neither calls fireAllFinished()?
ConcurrentHashMap<String, Boolean> taskToFinished = new ConcurrentHashMap();
taskToFinished.put("taskA", false);
taskToFinished.put("taskB", false);
public void checkForAllFinished() {
boolean allFinished = true;
for (Boolean taskFinished = tasksToFinished.values()) {
if (!taskFinished) {
allFinished = false;
break;
}
}
if (allFinished) {
fireAllFinished()
}
}
//Thread1
public void run() {
taskToFinished.put("taskA", true);
checkForAllFinished();
}
//Thread1
public void run() {
taskToFinished.put("taskB", true);
checkForAllFinished();
}
(I've omitted some of the thread creation code. I hope the intent is clear)
update: I already saw this more general question: Is iterating ConcurrentHashMap values thread safe?, but wanted to confirm my specific point as
"at some point"
is generally an imprecise concept when dealing with multi-core machines running code out of order, two threads may update different segments of the map 'at the same time', and there is by design no way to lock the entire ConcurrentHashMap.
Reading the documentation for ConcurrentHashMap
...
Retrievals reflect the results of the most recently completed update operations holding upon their onset. (More formally, an update operation for a given key bears a happens-before relation with any (non-null) retrieval for that key reporting the updated value.)
and
For aggregate operations such as putAll and clear, concurrent retrievals may reflect insertion or removal of only some entries. Similarly, Iterators, Spliterators and Enumerations return elements reflecting the state of the hash table at some point at or since the creation of the iterator/enumeration.
It's not worded clearly, but what most recently completed and at or since are supposed to mean that map operations and iterator creation are sequentially consistent.
Using your example, if we call map put A, and value check B you have...
T1: A -> B
T2: A -> B
A happens before B, but T1 and T2 happen concurrently. What sequentially consistent means is some valid sequence between the two needs to occur as long as A happens before B. However, any sequencing between T1 and T2 is valid.
e.g.
T1:A -> T1:B -> T2:A -> T2:B
T1:A -> T2:A -> T2:B -> T1:B
So when the code actually runs, any valid ordering can happen but, T1:B or T2:B (the check) must happen last. So, fireAllFinished
gets called either once or twice. Linear synchronization would restrict that even further to an explicit ordering between all the events.
Iterating over the entire map may be a bit expensive though, and it may be simpler to just use an AtomicInteger
or another synchronization mechanism like a ConcurrentLinkedQueue
.