Ialmost forgot that I have this story in my to-be-blogged list. I was assigned to perform performance tuning on a system in my previous project. I found a concurrency bottleneck and rearrange the synchronization pattern to gain noticeably throughput. I later had a chance to read about the Instance Confinement technique which could be applied with the case. The technique uses the normal object encapsulation to make the code easier to understand in term of concurrent access aspect.
I will change the details of the system I was working on then to simplify the example. The class below represents my image chart.
public class NotSoSimpleChart { private Object paintLock = new Object(); private Object writeLock = new Object(); public void renderImage(){ //some code for rendering preparation //.... synchronized(paintLock){ //some code for rendering //.... paintLock.notify(); } } public void writeImage(){ synchronized(writeLock){ //some code for writing image //.... } } }
The class uses two private lock objects to coordinate concurrent access. In my real production code, the “writeLock” object is the bottleneck and the system will gain better performance if I re-arrange the code regarding writing chart image. The detail of the problem is not what I want to talk about here. The point is that it is quite hard to analyse all the concurrent access scenarios of the class before I gain enough knowledge to re-arrange it. Although each lock is used in different usage scenario but I still need to walk through the code to determine if the code guarded by a lock will affect the variables used in the code guarded by another lock (which should be a defect hidden there for long time ).
In my real case, it was lucky for me that the code for writing image was short enough. I was quite sure the two locks were used completely separated so I performed the refactoring with confidence. If the code were more complex then it would be much more difficult for me to analyze all the possible states of the instance. I later came across the “Instance Confinement technique” in Java Concurrency in Practice (really excellent book). The technique is described in the book as following.
Encapsulating data within an object confines access to the data to the object’s methods, making it easier to ensure that the data is always accessed with the appropriate lock held.
Confinement makes it easier to build thread-safe classes because a class that confines its state can be analyzed for thread safety without having to examine the whole program.
The technique could be applied to my problem above. I could extract out the logic for writing image file to ImageFileWriter and make the class responsible for managing its own concurrent access policy.
public class SimplerChart { private Object paintLock = new Object(); private ImageFileWriter imgWriter = new ImageFileWriter(); public void renderImage(){ //some code for rendering preparation //.... synchronized(paintLock){ //some code for rendering //.... paintLock.notify(); } } public void writeImage(){ imgWriter.writeImage(getImage(), generateFileName() ); } } class ImageFileWriter{ private Object writeLock = new Object(); public void writeImage(BufferedImage im, String fileName){ synchronized(writeLock){ //some code for writing image //.... } } }
Now I don’t need to analyse the internal logic for writing image file together with the logic for rendering image. I can investigate it at the higher level by looking at how the instance of ImageFileWriter is accessed. The Chart object relies on the image writer instance to guarantee that any access to the writer instance will be thread-safe. So the chart object doesn’t need to provide extra synchronization which risk interfering with other variables in the object.
Encapsulating the logic in the image writer class makes the code more maintainable. The states will never be published out to external parties (they are confined to the instance). I can analyse the thread safety aspect of the logic without the need to look outside the class since the class is in control of all the possible ways to change its states.
It is amazing to see that the normal encapsulation principle can be used not just for managing complexity of internal details but also for managing concurrent access policy of an object.
We could applied the technique with paintLock too but am not sure what is the best way to encapsulate this wait/notify scenario. May be the Future pattern?