简体   繁体   中英

Confused about how java synchronization works

I have this example code based on a sample I found on the web to illustrate how to use wait() and notify() . Let me preface the code sample with the following statement a Java textbook makes about wait and notify .

A thread can't invoke a wait or notify method on an object unless it owns that's object's lock

Now take a look at this code and the output:

public class ThreadTester
{
    public class Message {
        private String msg;
        public Message(String str){ this.msg=str;}
        public String getMsg() { return msg; }
        public void setMsg(String str) { this.msg=str;} 
    }

    public class Waiter implements Runnable{         
        private Message msg;
        public Waiter(Message m){
            this.msg = m;
        }
        public void run() {
            String name = Thread.currentThread().getName();
            synchronized (msg) {
                try{
                    System.out.println(name + " acquired lock to msg object. waiting to get notified");
                    msg.wait();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(name + " waiter done at time: " + System.currentTimeMillis());
            }
            System.out.println(name + " waiter giving up msg object lock");
        }
    }

    public class Notifier implements Runnable {      
        private Message msg;         
        public Notifier(Message msg) {
            this.msg = msg;
        }
        public void run() {
            String name = Thread.currentThread().getName();
            System.out.println(name + " started");
            try {
                Thread.sleep(5000);
                synchronized (msg) {
                    String localMesg = name + " acquired lock to msg object. Notifier work done.";
                    System.out.println(localMesg);
                    msg.setMsg(localMesg);
                    msg.notify();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }    
        }    
    }

    void runtest() {
        Message msg = new Message("process it");
        Waiter waiter1 = new Waiter(msg);
        new Thread(waiter1,"waiter1").start();

        Waiter waiter2 = new Waiter(msg);
        new Thread(waiter2, "waiter2").start();

        Notifier notifier = new Notifier(msg);
        new Thread(notifier, "notifier").start();
        System.out.println("All the threads are started");
    }

    public static void main(String [] args) {
        new ThreadTester().runtest();
    }
}

Here is the output:

waiter1 acquired lock to msg object. waiting to get notified
waiter2 acquired lock to msg object. waiting to get notified
All the threads are started
notifier started
notifier acquired lock to msg object. Notifier work done.
waiter1 waiter done at time: 1419745413721
waiter1 waiter giving up msg object lock

Now the question is, how can waiter2 or notifier threads acquire the msg object's lock when waiter1 thread is still holding it? That seems to directly contradict the Java textbook statement I cite above. What am I missing?

Thanks for any help.

In the pattern

synchronized (msg) {
  msg.wait();
}

the lock on msg is indeed taken by the synchronized statement. But the msg.wait() call temporarily releases the lock and waits for a notification. A later msg.notify() or msg.notifyAll() can satisfy that wait. When the wait ends, the lock on msg is taken again. In the example the Notifier thread does msg.notify() , which means that one of the two Waiter threads can have its msg.wait() satisfied. That doesn't happen immediately, because the msg.notify() is inside a synchronized (msg) block so the Notifier thread is holding the lock on msg . As soon as that block exits, though, whichever thread's msg.wait() got the notification will take back the lock on msg and proceed.

This pattern is fairly fragile. The Notifier thread starts by doing a 5 second sleep, to make sure that the two Waiter threads have each reached their msg.wait() . Without the sleep, it would be possible for Notifier to do msg.notify() before either Waiter thread had done msg.wait() , and the msg.notify() would have had no effect. Because of issues like this, it is usually better to use synchronization classes such as Semaphore from the java.util.concurrent package, rather than using synchronized and wait / notify .

It wouldn't make sense to hold the lock while waiting. If you held the lock while waiting, you'd never be able to notify it.

According to §17.2.1 (Wait) of the JLS :

Thread t is added to the wait set of object m, and performs n unlock actions on m.

So, that means whilst the thread is waiting upon notification, the lock is released so notifiers can acquire the lock and drain the wait set.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM