10

我正在尝试检查等待/通知在 java 中的工作方式。

代码:

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().

4

3 回答 3

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 over并且leaving 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");
        }
    }
于 2013-07-01T06:53:40.383 回答
5

对更新代码的回答:

Thread.sleep() javadoc:

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

如果在同步块中调用 Thread.sleep,其他线程将无法进入同步块。您不应该在同步块中执行耗时的任务以避免这种情况。

于 2013-07-01T07:18:17.047 回答
2

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

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

一种实现方式是使用wait(int)on lock 而不是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 的高级原语。例如,您可以使用CountDownLatchwhich 让一个线程等待,直到其他线程倒计时到零:

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

在这里你不需要同步任何东西,latch 会为你做所有事情。您可以使用许多其他原语 - 信号量、交换器、线程安全队列等。探索java.util.concurrent包。

也许更好的解决方案是使用更高级别的 API,例如Akka提供的。在那里,您可以使用ActorsSoftware transactional memory,它们可以轻松组合并让您免于大多数并发问题。

于 2013-07-01T08:09:39.580 回答