Here is a class to gather simple statistics during a multi-threaded batch process:
public final class Stats {
public enum Counter { INPUTS, PARSE_ERRORS, PROCESSING_ERRORS, SUCCESSES, };
private final EnumMap<Counter, AtomicInteger> values;
public Stats() {
values = new EnumMap<Counter, AtomicInteger>(Counter.class);
// Pre-fill the map:
for (Counter counter: Counter.values())
values.put(counter, new AtomicInteger());
}
public void increment(Counter counter) { values.get(counter).incrementAndGet(); }
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Entry<Counter, AtomicInteger> entry: values.entrySet())
sb.append(entry.getKey() + " = " + entry.getValue() + "\n");
return sb.toString();
}
}A single instance is shared among all worker threads. Each worker increments the appropriate counters as it progresses:stats.increment(Stats.Counter.INPUTS);The statistics are output when all work is done:
System.out.println(stats);
The important point here is that access to the values field is not synchronized, yet Stats is still thread-safe. This is because values is effectively immutable: after it has been pre-filled in the constructor and safely published through a final field, it is never structurally modified. Therefore it is safe to assume that all invocations of increment will see it in a consistent state.
On the other hand, here is an example where the modification of an EnumMap is not safely published:
public class UnsafeEnumMapMutation { // DOESN'T WORK
static enum Command { STOP }
private static final EnumMap<Command, Integer> MAP =
new EnumMap<Command, Integer>(Command.class);
public static void main(final String[] args) throws InterruptedException {
final ExecutorService executor = Executors.newFixedThreadPool(2);
final Runnable looping = new Runnable() {
public void run() {
int i = 0;
while (!MAP.containsKey(Command.STOP)) i++;
}
};
executor.submit(looping);
final Runnable stopper = new Runnable() {
public void run() { MAP.put(Command.STOP, 1); }
};
executor.submit(stopper);
executor.shutdown();
}
}As in my previous post, task stopper is trying to tell task looping to terminate. This time, the signal is the presence of a given key the map. However, this change is not protected by synchronization, and the map is not effectively immutable, since it is mutated after its initial publication as a final field. And again, this example loops when I run it with the -server option.

0 comments:
Post a Comment