Performance Zone is brought to you in partnership with:

I have been writing code since 1980. In 1999, I learned Java and after discovering IntelliJ the love was complete. XML and XSLT was a revelation in 2000, too bad the standards committees have done so much damage since. Contributed to the Flying Saucer open source XML and CSS rendered and published an open source XSLT generator, weffo. Currently employed by Google. Torbjörn is a DZone MVB and is not an employee of DZone and has posted 9 posts at DZone. You can read more from them at their website. View Full User Profile

Performance and Thread-Safe State Handling

03.25.2008
| 4937 views |
  • submit to reddit

In my previous post I introduced using the state pattern and an AtomicReference to help maintain thread safety. My reason for starting to play with the pattern was really to make it easier to analyze the code and prove correctness, but obviously the question of performance inevitably comes up and it is interesting to know the trade-offs.

I set up a benchmark where ten threads would simultaneously try to execute the same code and each thread would just hammer away for an obscenely large number of times. First up for the test was the simple lamp:

public interface Lamp {
  boolean isOn();
  void setOn(boolean on);
}

 

The code to execute was:

              b = lamp.isOn();
              lamp.setOn(!b);

I had an implementation with just a volatile field, an implementation with both methods synchronized, a hybrid implementation where the field was volatile and only the setter synchronized and an implementation using the state pattern. I also ran the bad non-thread-safe implementation. The results with the average time for one run through the code: volatile (669 ns), bad (697 ns), state (985 ns), hybrid (1570 ns), synchronized (2535 ns). Interestingly enough, the state pattern kicks butt. Actually the state pattern was even faster than volatile in the first tests I ran, but that is probably because I ran too few iterations to pay the full cost of garbage collection.

So how about a predicted worst case, the CoffeeMachine where the state changes on every method call?

public interface CoffeeMachine {
  void fillBeans(int beans);
  void fillWater(int water);
  Coffee makeCoffee(int water, int beans) throws OutOfWaterException, OutOfBeansException;
}

And the test code, same rules as above, ten threads whacking away:

          Random r = new Random();
....
              int w = r.nextInt(5);
              cm.fillWater(w);
              int b = r.nextInt(3);
              cm.fillBeans(b);
              try {
                cm.makeCoffee(w,b);
              } catch (OutOfWaterException e) {
                oow.getAndIncrement();
              } catch (OutOfBeansException e) {
                oob.getAndIncrement();
              }

 

This was a straight shoot-out between a synchronized implementation and a state pattern implementation. Result: synchronized (5021 ns), state (5333 ns). A slight victory for synchronization, yeah!

So how about adding some getters into the CoffeeMachine, how does that change things? The test code was changed to:

Random r = new Random(); 

....

int w = r.nextInt(5); 
if(cm.checkWater() < w*10)
cm.fillWater(w*100);
int b = r.nextInt(3);
if (cm.checkBeans() < b*10)
cm.fillBeans(b*100);
try
{
cm.makeCoffee(w,b);
}
catch (OutOfWaterException e)
{
ons.getAndIncrement();
}
catch (OutOfBeansException e)
{
offs.getAndIncrement();
}

With the getters present it makes sense to try a hybrid approach again. Result: state (2547 ns), hybrid (3675 ns), synchronized (4930 ns).

Conclusion: not only does the state pattern with an AtomicReference to an immutable internal state representation make it easier to manage code complexity, it is also beneficial from a performance point of view, being just slightly slower than synchronization in the worst case and twice as fast in normal cases.

Published at DZone with permission of Torbjörn Gannholm, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)