【问题标题】:Wait() / notify() synchronizationWait() / notify() 同步
【发布时间】:2013-07-01 06:51:26
【问题描述】:

我正在尝试检查等待/通知在 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() 时才会被打印出来。

【问题讨论】:

  • 你没有在同一个对象上同步
  • MyRunnable.this == r != t
  • @raul8 编辑问题并粘贴答案会使正确答案无效。最好再补充一个问题。
  • 我了解到您正在研究 wait/notify 的工作原理。但无论如何,我强烈建议您在代码中使用 Java 的 high level concurrent API。在低级并发代码中很容易犯难以发现的错误(正如您在本示例中所见)。

标签: java multithreading synchronization


【解决方案1】:

对象监控锁需要对同一个锁执行单次引用...

在您的示例中,您是Thread 实例上的waiting,但使用来自Runnablenotify。相反,您应该使用单个通用锁对象...例如

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

【讨论】:

  • 谢谢...但是我尝试了您的代码.. 但它仍然无法正常工作。有问题的代码已更新
  • 仅供参考Thread.sleep(两种形式)是一个静态方法,它总是让调用线程进入睡眠状态;因此Thread.currentThread().sleep(1000) 在语义上是多余的并且可能具有误导性(例如,调用t.sleep(1000) 会使调用线程进入睡眠状态,而不是t)。
【解决方案2】:

更新代码的答案:

来自Thread.sleep()javadoc:

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

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

【讨论】:

    【解决方案3】:

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

    如果您希望您的主线程在调用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");
            }
        }
    }
    

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-21
      • 2019-02-19
      • 2012-11-19
      • 1970-01-01
      相关资源
      最近更新 更多