I am planning to use this schema in my application, but I was not sure whether this is safe.
To give a little background, a bunch of servers will compute results of sub-tasks that belong to a single task and report them back to the central server. This piece of code is used to register the results, and also check whether all the subtasks for the task has completed and if so, report that fact only once.
The important point is that, all task must be reported once and only once as soon as it is completed (all subTaskResults are set).
Can anybody help? Thank you! (Also, if you have a better idea to solve this problem, please let me know!)
*Note that I simplified the code for brevity.
Solution I
class Task {
//Populate with bunch of (Long, new AtomicReference()) pairs
//Actual app uses read only HashMap
Map<Id, AtomicReference<SubTaskResult>> subtasks = populatedMap();
Semaphore permission = new Semaphore(1);
public Task set(id, subTaskResult){
//null check omitted
subtasks.get(id).set(result);
return check() ? this : null;
}
private boolean check(){
for(AtomicReference ref : subtasks){
if(ref.get()==null){
return false;
}
}//for
return permission.tryAquire();
}
}//class
Stephen C kindly suggested to use a counter. Actually, I have considered that once, but I reasoned that the JVM could reorder the operations and thus, a thread can observe a decremented counter (by another thread) before the result is set in AtomicReference (by that other thread).
*EDIT: I now see this is thread safe. I’ll go with this solution. Thanks, Stephen!
Solution II
class Task {
//Populate with bunch of (Long, new AtomicReference()) pairs
//Actual app uses read only HashMap
Map<Id, AtomicReference<SubTaskResult>> subtasks = populatedMap();
AtomicInteger counter = new AtomicInteger(subtasks.size());
public Task set(id, subTaskResult){
//null check omitted
subtasks.get(id).set(result);
//In the actual app, if !compareAndSet(null, result) return null;
return check() ? this : null;
}
private boolean check(){
return counter.decrementAndGet() == 0;
}
}//class
I assume that your use-case is that there are multiple multiple threads calling
set, but for any given value ofid, thesetmethod will be called once only. I’m also assuming thatpopulateMapcreates the entries for all usedidvalues, and thatsubtasksandpermissionare really private.If so, I think that the code is thread-safe.
Each thread should see the initialized state of the
subtasksMap, complete with all keys and all AtomicReference references. This state never changes, sosubtasks.get(id)will always give the right reference. Theset(result)call operates on an AtomicReference, so the subsequentget()method calls incheck()will give the most up-to-date values … in all threads. Any potential races with multiple threads calling check seem to sort themselves out.However, this is a rather complicated solution. A simpler solution would be to use an concurrent counter; e.g. replace the
Semaphorewith anAtomicIntegerand usedecrementAndGetinstead of repeatedly scanning thesubtasksmap incheck.In response to this comment in the updated solution:
The AtomicInteger and AtomicReference by definition are atomic. Any thread that tries to access one is guaranteed to see the “current” value at the time of the access.
In this particular case, each thread calls
seton the relevant AtomicReference before it callsdecrementAndGeton the AtomicInteger. This cannot be reordered. Actions performed by a thread are performed in order. And since these are atomic actions, the efects will be visible to other threads in order as well.In other words, it should be thread-safe … AFAIK.