Wait() / notify() synchronization

2019-02-05 01:39发布

问题:

I'm trying to check how wait/notify works in java.

Code:

public class Tester {
    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
        Thread t = new Thread(r);
        t.start();
        synchronized (t) {
            try {
                System.out.println("wating for t to complete");
                t.wait();
                System.out.println("wait over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("entering run method");
        synchronized (this) {
            System.out.println("entering syncronised block");
            notify();
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("leaving syncronized block");
        }
        System.out.println("leaving run method");
    }
}

Output Returned

wating for t to complete
entering run method
entering syncronised block
//sleep called
leaving syncronized block
leaving run method
wait over

I was expecting when notify() is executed the wait will be over & System.out.println("wait over"); will get printed. But it seems it only gets printed when t finished its run().

回答1:

Object monitor locks need to be performed a single reference of the same lock...

In your example you are waiting on an instance of the Thread, but using notify from the Runnable. Instead, you should use a single, common lock object...for example

public class Tester {

    public static final Object LOCK = new Object();

    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
        Thread t = new Thread(r);
        t.start();
        synchronized (LOCK) {
            try {
                System.out.println("wating for t to complete");
                LOCK.wait();
                System.out.println("wait over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class MyRunnable implements Runnable {

        public void run() {
            System.out.println("entering run method");
            synchronized (LOCK) {
                System.out.println("entering syncronised block");
                LOCK.notify();
                try {
                    Thread.currentThread().sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("leaving syncronized block");
            }
            System.out.println("leaving run method");
        }
    }
}

Output...

wating for t to complete
entering run method
entering syncronised block
leaving syncronized block
wait over
leaving run method

wait over and leaving run method could change positions depending on the thread scheduling.

You could try putting the sleep out side the synchronized block. This will release the monitor lock allowing the wait section to continue running (as it can't start until the lock is released)

    public static class MyRunnable implements Runnable {

        public void run() {
            System.out.println("entering run method");
            synchronized (LOCK) {
                System.out.println("entering syncronised block");
                LOCK.notify();
                System.out.println("leaving syncronized block");
            }
            try {
                Thread.currentThread().sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("leaving run method");
        }
    }


回答2:

Answer to updated code :

From Thread.sleep() javadoc:

Causes the currently executing thread to sleep (temporarily cease execution) for the specified number of milliseconds, subject to the precision and accuracy of system timers and schedulers. The thread does not lose ownership of any monitors.

If you call Thread.sleep while inside a synchronized block, other threads won't be able to enter the synchronized block. You should never do time consuming tasks while in a synchronized block to avoid this.



回答3:

Note (as others pointed out as well) that you have to use the same object for locking/synchronizing in both threads.

If you want your main thread to continue immediately after notify is called, you have to relinquish the lock temporarily. Otherwise wait will get called only after the secondary thread leaves the synchronized block. And it's never a good idea to keep a lock in a long running computation!

One way how to achieve is to use wait(int) on the lock instead of sleep, because wait releases the synchronization lock temporarily:

public class Tester {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();
        synchronized (lock) {
            try {
                System.out.println("wating for t to complete");
                lock.wait();
                System.out.println("wait over");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class MyRunnable implements Runnable {
        public void run() {
            System.out.println("entering run method");
            synchronized (lock) {
                System.out.println("entering syncronised block");
                lock.notify();
                try {
                    lock.wait(1000); // relinquish the lock temporarily
                } catch (InterruptedException ex) {
                    System.out.println("got interrupted");
                }
                System.out.println("leaving syncronized block");
            }
            System.out.println("leaving run method");
        }
    }
}

However, using these low-level primitives can be very error prone and I'd discourage from using them. Instead, I'd suggest you to use Java's high-level primitives for that. For example, you can use CountDownLatch which lets one thread wait until other threads count down to zero:

import java.util.concurrent.*;

public class TesterC {
    private static final CountDownLatch latch = new CountDownLatch(1);

    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.start();

        System.out.println("wating for t to complete");
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("wait over");
    }

    static class MyRunnable implements Runnable {
        public void run() {
            System.out.println("entering run method");
            try {
                latch.countDown();
                Thread.sleep(1000);
            } catch (InterruptedException ex) {
                System.out.println("got interrupted");
            }
            System.out.println("leaving run method");
        }
    }
}

Here you don't have to synchronize anything, the latch does everything for you. There are many other primitives you can use - semaphores, an exchanger, thread-safe queues, etc. Explorer the java.util.concurrent package.

Perhaps even better solution is to use even higher level API, such as Akka provides. There you work with Actors or Software transactional memory, which can be composed easily and spare you of most of concurrency issues.