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]
评论:

Doesn't this give rise to a thundering herd? Using notifyAll() when you know that the first thread to grab the lock will service *all* the messages is inefficient. Perhaps the design needs to be tuned to share out the work fairly between servicing threads and only wake up threads that need to be woken up when work is available.

发表于 Grahamm 在 2004年12月15日, 01:41 上午 GMT+08:00 #

Thanks, Grahamm.
Yes, his solution is not so good, it's better to design an more fair way "that only wake up threads that need to be woken up when work is available" :P. BTW: notifyAll will awake all the waiting thread in an unexpected order. So, if we wanna the thread to be awaked in an expected order, we must give own solution. Such as "Specific Notification Pattern".

发表于 Elan Meng 在 2004年12月15日, 11:31 上午 GMT+08:00 #

What is the Specific Notification Pattern, plz tell something about that, :P

发表于 Winters.Mi 在 2004年12月15日, 02:54 下午 GMT+08:00 #

Hi Winters, Please check "here. I think you can got the idea. :P

发表于 Elan Meng 在 2004年12月15日, 03:12 下午 GMT+08:00 #

Sorry, the link should be here.

发表于 Elan Meng 在 2004年12月15日, 03:12 下午 GMT+08:00 #

BTW:
The author of this article is Peter Hagger - the author of "Pratical Java".

发表于 Elan Meng 在 2004年12月15日, 03:14 下午 GMT+08:00 #

发表一条评论:

该日志评论功能被禁用了。