20041214 星期二 2004年12月14日

Using spin lock in your code

Today, my friend ask me to review some code of him, his code will encounter some unexpected error. He's very confused and worried. After digging into the code, I found the problem.
Here's the code slice :
    private boolean done;
    private Message[] messages;
    ... ...
    synchronized(this) {
        if(!done || messages ==null)                                     //    1
           try {
                wait();                                                  //    2 
           } catch (InterruptedException e) {
                e.printStackTrace();
           }
           ...
           dispatchMessage(messages);                                   //    3
           ...
    }
    dispatchMessage(Message[] messages) {
        int length = messages.length;                                   //    4
        ... ...
        messages = null;
    }
When the program run, it will throw NullPointerException sometimes, though very infrequent. And just because of its infrequence, it's hard to debug.
But when go through this code slice, I think Java Expert can learn where's the problem.
Try to imagine such scenario:
1) messages is null
2) Thread 1 check the condition in position 1and waiting  in position 2, and it will release the lock, So other thread can obtain it;
3) Thread 2 check the condition in position 1and waiting  in position 2 too;
4) When we get the message and notify all threads using notifyAll(), both thread 1 and thread 2 will be awaked but only one thread will obtain the lock.
5) Assume thread 1 will get the lock, and it will dispatch the message using dispatchMessage. after dispatching the message, it unreference the messages.
6) When thread 1 finished dispatching the message and release the lock. Thread 2 will obtain the lock, however,  NOW, messages is null, so it will throw NPE.
Then, how to solve the problem? Yeah, it should use spin lock here. So, it will re-check the condition after being awaked and will avoid such problem.
That is, it should be
    while(!done || message ==null)
    ... ...
And this trick is called spin lock(reference1) , you can see detail discussion in this book (practice 54).
BTW: You also can find this trick in "Effective Java", in item 50 - "Never use wait out of loop". And, this two book is really good and worth reading. :-)

Reference:
1) "Pratical Java" - Peter Haggar
2) "Effective Java" - Joshua Bloch
( 2004年12月14日, 09:29:59 下午 GMT+08:00 ) Permalink 评论 [6]