|
|
|
Back to newsletter 032 contents
I need to use a StringBuffer to repeatedly build strings. Should I make it static and reuse it?
This question, and varieties of it, is asked a lot. Whenever you are using a temporary object to do some intermediate processing, there is a reasonable temptation to reuse the object where that is possible. For example, the following method uses a temporary StringBuffer to construct a string:
public String sayHello(String title, String name) { StringBuffer s = new StringBuffer(); s.append("Hello "); s.append(title); s.append(' '); s.append(name); s.append(", how are you today?"); return s.toString(); }
You might think to yourself that each run through this method a StringBuffer is created and thrown away, just to make a String. This seems to be inefficient doesn't it? So what can you do about it? Easy, make the StringBuffer object a static, so that you can reuse it:
private static StringBuffer SayHelloTempStringBuffer = new StringBuffer(); public String sayHello(String title, String name) { StringBuffer s = SayHelloTempStringBuffer; s.setLength(0); s.append("Hello "); s.append(title); s.append(' '); s.append(name); s.append(", how are you today?"); return s.toString(); }
Hey presto, we have eliminated all those temporary StringBuffers. Great! Well yes and no. It is true that this particular tuning technique is a valid tune. Reusing objects is a recommended tuning technique. But reusing objects is a tradeoff, just like many other tuning techniques. In exchange for reducing the number of objects being used, you now have one very long lived object. You also have the problem that you need to understand StringBuffer internals in order to avoid some subtle effects. For example, a new StringBuffer is created with a default internal char array of a specific size (16). But the static StringBuffer will grow its internal char array to the largest size needed by any of the constructed strings, and that will not get shrunk even if subsequent strings are much shorter. Each constructed String will then use a much larger char array than is necessary, potentially causing huge inefficiencies.
Worse than that is the thread safety of the method. The original method is thread safe, since each invocation uses a StringBuffer object local to the thread. But, although StringBuffer is itself thread-safe, the new "tuned" method is not, since two threads would use the same StringBuffer object, and there are many potential context-switch points between calls to the StringBuffer methods. Methods which are not thread-safe have a tendency to cause bugs sooner or later as changes are made to a program and developers forget that a particular method is not thread-safe.
So is this then a technique to avoid? No, reusing objects in this way is a valid tuning technique, as I said. The problem here is that of premature tuning. Why did we change the method to use a static? Because we thought that all those temporary StringBuffer objects looked inefficient. But one of the first lessons you should learn about tuning is not to guess. Don't guess that there may a performance problem. Implement the code simply, with good coding practices, then measure the performance and find the bottlenecks. If there is a bottleneck, use one of the hundreds of performance tuning techniques available (including reusing objects just like the example above) to improve the performance of the bottleneck. Then document clearly what you've done. (I like to keep the old code in comments.)
The important thing to remember is that you shouldn't anticipate problems which may never occur. Just because some code looks inefficient doesn't mean it will be a problem. The code may in fact be grossly inefficient, but if it is only ever run once or twice it will never matter. Or it may be more efficient than you expect. Or the JVM you run on may be particular good at running that type of code.
Measure performance, and tune identified bottlenecks. Don't guess.
(Note, in case you actually need this tune after having measured that the bottleneck is caused from a temporary and that a static would improve performance: you can avoid the threading issues by making the method synchronized or by using a ThreadLocal object to hold one static reusable object per thread).
The JavaPerformanceTuning.com team
Back to newsletter 032 contents