【问题标题】:Call to Java Object's wait() breaks thread synchronization调用 Java 对象的 wait() 会中断线程同步
【发布时间】:2010-06-29 12:04:37
【问题描述】:
public class Main2 {
    public static void main(String[] args) {
        new Test2().start();
        new Test2().start();
    }
}

class Test2 extends Thread {
    @Override
    synchronized public void run() {
        try {
            System.out.println("begin wait");
            wait();
        } catch (Exception ex) {
        }
    }
}

作为运行测试的实际结果: 开始等待, 开始等待, 从两个线程两次。 与预期结果的对比: 开始等待, 仅来自两个线程之一的一次,因为在同步的 run() 方法中调用了 wait()。 为什么调用 Object 的 wait() 会中断线程同步?

比很多!


    public class Main3 {

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

class Test3 implements Runnable {
    synchronized public void run() {
        try {
            System.out.println("begin wait");
            wait();
        } catch (Exception ex) {
        }
    }
}

@akf 和@肖恩欧文

感谢您的回复。抱歉我的错误,现在我修改了代码,将同步放在同一个对象的 run() 上,结果仍然是:开始等待,开始等待,两次。

@akf

wait 会释放那个锁 同步已经抓住了,将是 通知线程后重新获取。

你能详细说明一下吗?

【问题讨论】:

    标签: java multithreading concurrency


    【解决方案1】:
    1. 在此示例中同步的对象不是类,而是实例,因此每个新的 Test2 对象将在不同的监视器上同步。
    2. 您可能在这里寻找的方法是sleep,而不是waitwait 会释放synchronized 已经抓取的锁,一旦通知线程就会重新获取。

    请注意,为了让您的测试正常工作,您需要锁定一个公共对象。如果您想查看wait 的实际效果,我已经编写了一个简单的应用程序,该应用程序将弹出一个带有“通知”按钮的框架。将启动两个线程等待一个公共对象,并在按下按钮时依次收到通知。

    public static void main(String[] args)
    {
        final Object lock = new Object(); 
        
        final JFrame frame = new JFrame("Notify Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JButton button = new JButton("Notify");
        button.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent evt) {
                synchronized(lock) {
                    lock.notify();
                }
            }
        });
        frame.add(button);
        
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                frame.setVisible( true );
            }
        });
    
        new Thread(new Runnable() {
            public void run() {
                synchronized(lock) {
                    try {
                        System.out.println("1. starting");
                        lock.wait();
                        System.out.println("1. step 1");
                        lock.wait();
                        System.out.println("1. step 2");
                    } catch (InterruptedException ie) {
                        ie.printStackTrace();
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            public void run() {
                synchronized(lock) {
                    try {
                        System.out.println("2. starting");
                        lock.wait();
                        System.out.println("2. step 1");
                        lock.wait();
                        System.out.println("2. step 2");
                    } catch (InterruptedException ie) {
                        ie.printStackTrace();
                    }
                }
            }
        }).start();
    
    }
    

    对于wait 的简单解释,JavaDoc 总是一个很好的起点:

    使当前线程等待,直到另一个线程为此对象调用 notify() 方法或 notifyAll() 方法。换句话说,此方法的行为与它只是执行调用 wait(0) 完全相同。

    当前线程必须拥有该对象的监视器。线程释放此监视器的所有权并等待,直到另一个线程通过调用 notify 方法或 notifyAll 方法通知在此对象的监视器上等待的线程唤醒。然后线程等待,直到它可以重新获得监视器的所有权并恢复执行。

    【讨论】:

    • 感谢示例和 javadoc 参考!
    • 请注意,唤醒哪个线程的选择是任意的。一个好的实现将是公平的,并且会按照调用 wait() 的顺序通知线程,但这不是必需的。所以唯一的限制是 [N]. step 1 发生在 [N]. step 2 之前,其中 N 始终为 1 或 2。
    • @Mark Peters,这是一个很好的观点 - 尽管它可能看起来不是任意的(即,经验证据可能会试图说服你它是有序的),但不能保证。
    【解决方案2】:

    您有两个不同的 Test2 对象。同步方法锁定对象。他们没有获得相同的锁,所以不应该打印两次。

    【讨论】:

      【解决方案3】:

      一个可以帮助你的简单例子是这样的:

              public class test {
                  public static void main(String[] args) {
                      Prova a=new Prova();
                      new Test2(a).start();
                      new Test2(a).start();
                  }
              }
              class Prova{
                  private boolean condition;
      
                  public void f(){
      
                      while(condition){
                          //Thread.currentThread  Returns a reference to the currently executing thread object.
                          //Thread.getName() return name Thread
                          System.out.println(Thread.currentThread().getName()+" begin wait");
                          try{
                              wait();
                          }catch(InterruptedException c){return;}
                      }       
      
                      System.out.println(Thread.currentThread().getName()+" first to take the mutex");
                      condition=true;
      
                  }
              }
              class Test2 extends Thread {
                  private Prova a;
                  private static boolean condition;
      
      
                  public Test2(Prova a){
                      this.a=a;
                  }
                  @Override
      
                   public void run() {
                     synchronized(a){
                      try {           
                          a.f();           
                      } catch (Exception ex) {
                      }
                     }
                  }
              }
      

      在这种情况下,两个线程同步一个对象,第一个获取锁释放消息,第二个等待。在这个例子中使用条件变量

      【讨论】:

        【解决方案4】:

        等待/通知机制总结:

        1)当前线程到达一个对象的同步代码块,其中包含对wait()的调用,它与其他线程竞争锁(对象的监视器),作为赢家它执行块直到遇到 wait() 调用。

        2)通过调用wait(),当前线程将锁释放给其他竞争线程,然后停止执行,等待另一个成功获得锁的线程发送通知。

        JavaDoc:

        线程成为 对象的监视器在三个之一 方式:

        •通过执行同步实例 该对象的方法。

        •通过执行 同步语句的主体 在对象上同步。

        •对于 Class 类型的对象,通过执行 同步的静态方法 类。

        3)另一个线程到达同一个对象的另一个同步代码块,其中包含对 notify/notifyAll() 的调用,它与其他线程竞争锁,作为赢家它执行块直到完成对 notify/notifyAll() 的调用。它将通过调用 wait() 或在块执行结束时释放锁。

        4) 在接收到 notify/notifyAll() 时,当前线程竞争锁,作为获胜者,执行在它停止的地方继续。

        简单示例:

            public class Main3 {
        
            public static void main(String[] args) {
                Test3 t = new Test3();
                new Thread(t).start();
                new Thread(t).start();
                try {
                    Thread.sleep(1000);
                } catch (Exception ex) {
                }
                t.testNotifyAll();
            }
        }
        
        class Test3 implements Runnable {
        
            synchronized public void run() {
        
                System.out.println(Thread.currentThread().getName() + ": " + "wait block got the lock");
                try {
                    wait();
                } catch (Exception ex) {
                }
                System.out.println(Thread.currentThread().getName() + ": " + "wait block got the lock again");
                try {
                    Thread.sleep(1000);
                } catch (Exception ex) {
                }
                System.out.println(Thread.currentThread().getName() + ": " + "bye wait block");
        
            }
        
            synchronized void testNotifyAll() {
                System.out.println(Thread.currentThread().getName() + ": " + "notify block got the lock");
                notifyAll();
                System.out.println(Thread.currentThread().getName() + ": " + "notify sent");
                try {
                    Thread.sleep(2000);
                } catch (Exception ex) {
                }
                System.out.println(Thread.currentThread().getName() + ": " + "bye notify block");
            }
        }
        

        输出:

        Thread-0(或 1):等待块得到了 锁定

        Thread-1(或 0):等待块得到 锁

        main: 通知块得到了 锁定

        main:通知已发送

        main:再见通知块

        Thread-0(或 1):等待块 又拿到锁了

        Thread-0(或 1):再见 等待块

        Thread-1(或 0):等待块 又拿到锁了

        Thread-1(或 0):再见 等待块

        【讨论】:

          猜你喜欢
          • 2012-04-08
          • 1970-01-01
          • 1970-01-01
          • 2011-10-05
          • 2021-10-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多