Tuesday, January 22, 2008

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.

14 comments:

Carl Rosenberger said...

You are changing the semantics of synchronized. Interesting. Some questions:
- How would a user annotate a particular synchronized block to be treated as a read lock?
- What if you sometimes also want to do writing within a block? Do you support read/write locks also?
- A synchronized block not only marks code blocks for exclusive access by a single thread, it also is a memory boundary: Threads are forced to synchronize their local memory with global memory when they enter the block. Do you preserve this behaviour? If yes, how do you do it without a synchronized block?

Taylor said...

Carl,

good questions - let me try to answer them.

1) if you mean to use JDK1.5 annotations, then I have provided in our forge an annotations package which allows Terracotta to work with annotations. In that way, you would simply put the annotation @AutolockRead in front of the method you want to have read locked. If you meant the word "annotate" more generically, then the Terracotta config paints the method for Terracotta, which does the conversion. Wildcards are supported, so you can be as broad or specific as you like when specifying methods to be read locked.

I'll go back and add the Terracotta config that makes all this happen so it is more clear.

2) Reading under a write lock doesn't require anything special - a write lock has mutual exclusion so reads are protected as well as writes - so the key here is just write locks. In a pure Java context, all you need is synchronized. In a Terracotta context, the default behavior of locks is to be write, but they can be modified to be write, synchronous-write, read, or concurrent. The Terracotta documentation details the difference between these kind of locks.

3) Correct. Terracotta honors "happens-before". All changes are applied before a lock is granted - whether its intervm or intravm. In the intervm case there are still (jvm) locks (synchronization) being acquired but it's not mutually exclusive for the read case - think back to my article that says it is possible to build read locking with synchronized - in essence Terracotta is just that - clearly it does much more but in this trivial case it is just doing the hard work of converting write locks (mutual exclusion) into read locks and transparently replacing the synchronized with this specialized implementation. While it's not exactly what happens, you could imagine how this works by thinking that Terracotta replaces your method synchronization with a ReadWrite lock that only does a read lock from the util.concurrent package.

Jacquouille-La-Fripouille said...

Sorry...

Remove my previous comment...

Taylor said...

removed...

Robse said...

So... the advantage is, reads are concurrent but any write are synchronized as before?

Taylor said...

Right reads are concurrent and mutually exclusive with writes (which are also mutually exclusive of one another)

Christian said...

Hey Taylor,

This is probably a dumb question...

I'm wondering why the code in Main.java doesn't execute serially. Isn't every thread trying to obtain the same lock? (instance's lock in this case.)

Thanks!

Taylor said...

@christian,

that's the point of the post - to show how Terracotta can make that code execute in parallel as if it was acquiring a read lock, not a mutually exclusive write lock (which would normally execute in serial, as you say).

Christian said...

It all makes sense now. Thanks!

Anonymous said...

There are ed hardy shirts
,pretty ed hardy shirt for men, ed hardy womens in the ed hardy online store designed by ed hardy ,many cheap ed hardy shirt ,glasses,caps,trouers ed hardy shirts on sale ,
You can go to edhardyshirts.com to have a look ,you may find one of ed hardy clothing fit for you
http://straighthairgirl.blog126.fc2.com
http://www.seriousblogging.com/crazygirlsshirts
http://www.free-blog-site.com/iammyself
http://d.hatena.ne.jp/hotfishing

puma mens shoes
nike air max ltd
NIKE air shoes

nike max ltd
orange converse
adidas shoes
nike shoes
puma shoes
addidas shoes
cheap converse shoes
cheap nike shoes
cheap puma shoes

Anonymous said...

ghkkjf I like your blog. Thank you. They are really great . Ermunterung ++ .
Some new style Puma Speed is in fashion this year.
chaussure puma is Puma shoes in french . Many Franzose like seach “chaussure sport” by the internet when they need buy the Puma Shoes Or nike max shoes. The information age is really convenient .
By the way ,the nike max ltd is really good NIKE air shoes ,don’t forget buy the puma mens shoes and nike air max ltd by the internet when you need them . Do you know Nike Air Shoes is a best Air Shoes . another kinds of Nike shoes is better . For example , Nike Air Rift is good and Cheap Nike Shoes .the nike shox shoes is fitting to running.
Spring is coming, Do you think this season is not for Ugg Boots? maybe yes .but this season is best time that can buy the cheap ugg boots. Many sellers are selling discounted. Do not miss . Please view my fc2 blog and hair straighteners blog.
.thank you .

I like orange converse shoes ,I like to buy the cheap converse shoes by the internet shop . the puma shoes and the adidas shoes (or addidas shoes) are more on internet shop .i can buy the cheap nike shoes and cheap puma shoes online. It’s really convenient.
Many persons more like Puma basket shoes than nike air rift shoes . the Puma Cat shoes is a kind of Cheap Puma Shoes .
If you want to buy the Cheap Nike Air shoes ,you can buy them online. They are same as the Nike Air shoes authorized shop. Very high-caliber Air shoes and puma cat shoes . the cheap puma shoes as same as other.

Tom Gordon said...

Argh, yet another external magic configuration that changes the language semantics. Why not just say what you're shilling for and state the advantages of it?

karmakaze said...

Teracotta may very well do something useful. Unfortunately this example doesn't demonstrate it.
The synchronized method is on the object instance and the main creates ten instances therefore ten locks, so no big deal. Run the same code without Teracotta and see. If it was a static synchronized method, or if the method was called ten times on the same object, then it would be interesting.

karmakaze said...

It is on the same instance, new Thread reuses the Runnable. Noted. (or remove my comment either way).