Stupid JVM Tricks - Read locks from just synchronized
I bet if you asked someone in an interview what the synchronized block in Java does, they are likely to answer something along the lines of "protected sections" of code, if you are lucky they might answer with a deeper technical understanding, with terminology such as "atomicity," "mutual exclusion," "monitors," and the like.
I will further bet that if you asked if it's possible for Java's synchronized keyword to produce read lock semantics (e.g. concurrent access), you would hopefully get a resounding no. Ok, well, if your interviewee is clever enough, they might tell you that you could build a read lock mechanism, but that synchronized itself only provides you with write lock semantics - that is to say, mutual exclusion.
So if I ask you the very same question - what's your answer?
Mine is ... YES!
Let me show you how. First, here's my code (in a file called Main.java):
public class Main implements Runnable
{
public static final Main instance = new Main();
public void run()
{
enterMonitor();
}
public synchronized void enterMonitor()
{
System.out.println("I'm in the synchronized block"); System.out.flush();
try { Thread.currentThread().sleep(5000); }
catch (InterruptedException ie) { System.out.println("Interrupted"); }
}
public static void main(String[] args) throws Exception
{
for (int i = 0; i < 10; i++) {
new Thread(instance).start();
}
}
}
And here's my output:
I'm in the synchronized block
I'm in the synchronized block
I'm in the synchronized block
I'm in the synchronized block
I'm in the synchronized block
I'm in the synchronized block
I'm in the synchronized block
I'm in the synchronized block
I'm in the synchronized block
I'm in the synchronized block
(Note that all statements above were printed at the same time, not serially)
Now, I hope you are wondering how I did that. If not, this post really isn't for you. If you are, ok, ok, I admit it. Vanilla synchronized really can't give you read semantics, but with a bit of sleight of hand, Terracotta can do it for you.
The way I made the above output happen is that I configured Terracotta to treat the synchronized block as a read lock. The rest is simple - Terracotta manages the locking, and since it was told to make the particular lock in question a read lock, lo and behold, I was able to get fully concurrent access to the code inside the synchronized block.
I know what you are thinking now - holy synchronized block batman! That could be a disaster for code that wasn't expecting to be run concurrently! And you'd be right - except Terracotta is smarter than that. Let's suppose I had accidentally surrounded some code that made field updates (performing a write) to my object with a synchronized block, and told Terracotta to make that into a read lock. If I do that, and run the code with Terracotta, it will tell me (by throwing an Exception) at the point of modification that I made a mistake and my code was mistakenly trying to make an update to an object under a read lock. In other words, no, using read locks with Terracotta is not dangerous at all, and it can really boost the performance of your app.
Update: To see how simple this is, let me post the relevant bit of Terracotta config that converts the above synchronized into a read lock:
<locks>
<autolock>
<method-expression>void Main.enterMonitor()</method-expression>
<lock-level>read</lock-level>
</autolock>
</locks>
For all the details, see my full write-up in the Terracotta Cookbook - a really great place to see simple examples that demonstrate the power of Terracotta.