Wait()/ notify()同步

时间:2013-07-01 06:51:26

标签: java multithreading synchronization

我正在尝试检查java中的wait / notify如何工作。

代码:

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");
    }
}

输出已退回

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

我期待当执行notify()时,等待将结束& System.out.println("wait over");将被打印出来。但它似乎只有在t完成run()时才会打印出来。

3 个答案:

答案 0 :(得分:11)

对象监视器锁需要执行相同锁的单个引用...

在您的示例中,您waiting的实例Thread,但使用notify中的Runnable。相反,您应该使用单个公共锁定对象...例如

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");
        }
    }
}

<强>输出...

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

wait overleaving run method可能会根据线程安排更改位置。

您可以尝试将synchronized块放在一边。这将释放监视器锁定,允许wait部分继续运行(因为它在释放锁之前无法启动)

    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");
        }
    }

答案 1 :(得分:5)

回答更新的代码:

来自Thread.sleep() javadoc:

  

导致当前正在执行的线程休眠(暂时停止执行)   指定的毫秒数,取决于系统计时器的精度和准确性   和调度程序。 该主题不会失去任何监视器的所有权

如果在同步块内部调用Thread.sleep,则其他线程将无法进入同步块。在同步块中,您永远不应该耗费时间来避免这种情况。

答案 2 :(得分:1)

注意(正如其他人指出的那样)你必须在两个线程中使用相同的对象来锁定/同步。

如果您想在调用notify后立即继续主线程,则必须暂时放弃锁定。否则,只有在辅助线程离开wait块后才会调用synchronized。在长时间运行的计算中保持锁定永远不是一个好主意!

如何实现的一种方法是在锁上使用wait(int)而不是sleep,因为wait会暂时释放同步锁:

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");
        }
    }
}

然而,使用这些低级原语可能非常容易出错,我不鼓励使用它们。相反,我建议你使用Java的高级原语。例如,您可以使用CountDownLatch让一个线程等待,直到其他线程倒计数为零:

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");
        }
    }
}

在这里你不必同步任何东西,闩锁为你做了一切。您可以使用许多其他原语 - 信号量,交换器,线程安全队列等。资源管理器java.util.concurrent包。

或许更好的解决方案是使用更高级别的API,例如Akka提供的。在那里,您可以使用ActorsSoftware transactional memory,它可以轻松编写,并且可以免除大多数并发问题。