【问题标题】:What is wrong with it?它有什么问题?
【发布时间】:2015-03-08 17:17:38
【问题描述】:

我已经看到很多 waitnotify 的例子,但我仍然有问题。

public class Main(){
    public static void main(String args[]) throws Exception {
        MyThread s = new MyThread();
        s.start();
    }
}

class MyThread extends Thread {
    public void run() {
        k();
    }

    public synchronized void k() {
        System.out.println("before wait");
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("do something after wait");
    }

    public synchronized void m() {
        for (int i=0;i<6;i++)
            System.out.println(i);
        notify();
    }
}

运行程序时我得到的唯一输出是:"before wait"

【问题讨论】:

  • 您面临的问题是什么?有什么例外吗?
  • 不,输出只有:“等待前”
  • 我想知道为什么人们给我的问题减分,而不回答它!
  • 您是否在代码中的任何位置调用MyThread.m()
  • 您在问题中遗漏了非常重要的信息,这就是原因。您应该清楚地说明您的问题是什么,而不仅仅是 something 是错误的。

标签: java multithreading


【解决方案1】:

您在main 中创建的线程调用MyThread#k() 进入等待状态。此时,该线程将不会做任何其他事情,直到它被唤醒或中断。但是代码中唯一可能被唤醒的地方是MyThread#m() 中的notify。由于您的程序中没有任何内容调用该方法,因此永远无法唤醒线程。

您可能想要的是在主程序中的s.start() 之后添加对s.m() 的调用。这样,您的主线程将执行唤醒线程所需的notify

不幸的是,这不太可能奏效。问题是s.start() 导致您创建的线程准备好运行,但不一定立即运行。您对s.m() 的调用很可能会在创建的线程执行任何操作之前完成。然后你仍然会得到与以前完全相同的结果,除了你会看到在before wait 之前打印出的整数 0..6。 notify 不会做任何事情,因为子线程还没有执行它的wait。 (顺便说一句,由于MyThread#k()MyThread#m() 都是同步的,因此增加MyThread#m() 中的循环限制不会改变任何事情......子线程将无法进入MyThread#k()MyThread#m() 正在运行。您可以通过将 notify 放在同步块中来改进它,而不是使所有 MyThread#m() 同步。)

您可以尝试通过在主程序中的s.m() 之前添加Thread.sleep(1000) 来解决此问题。这几乎肯定会起作用,因为您的主线程将产生执行,让您的 JVM 有机会安排子线程进行一些有用的工作。当主线程从睡眠中唤醒并执行其s.m() 调用时,子线程可能已经执行了它的wait,然后您将看到您的do something after wait 消息。

但这仍然很糟糕,因为它仍然取决于安排您无法真正控制的事件。仍然没有保证wait 将在notify 之前发生。

这就是为什么在使用等待/通知时,您通常应该安排进行某种可靠的测试,以确定您等待完成的事情是否真的发生了。这应该是一个条件,一旦它变为真,至少在随后执行测试之前将保持真。然后你的典型等待循环看起来像这样:

while (!isDone()) {
    synchronized(monitorObject) {
        try {
            monitorObject.wait();
        } catch (InterruptedException e) {
        }
    }
}

将整个事情放在一个循环中可以解决过早醒来,例如由于 InterruptedException。

如果在执行此代码时所需的工作已经发生,则不会发生wait,并且由执行该工作的代码执行的notify 是空操作。否则,此代码将等待,完成工作的代码最终将执行notify,它将根据需要唤醒此代码。当然,在执行notify 时,等待条件(上面的isDone())为真并至少在测试之前保持为真,这一点至关重要。

这是包含正确等待循环的代码的更正版本。如果您注释掉 Thread.sleep() 调用,您可能不会看到 waiting 消息,因为工作将在等待循环开始之前完成。包括 sleep 在内,您可能会看到 waiting 消息。但无论哪种方式,程序都会正常运行。

public static void main(String[] argv) throws Exception {
    MyThread s = new MyThread();
    s.start();
    Thread.sleep(1000);
    s.m();
}

class MyThread extends Thread {

    @Override
    public void run() {
        k();
    }

    private boolean done = false;

    public void k() {
        System.out.println("before wait");
        while (!done) {
            System.out.println("waiting");
            synchronized (this) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }

        System.out.println("do something after wait");
    }

    public void m() {
        for (int i = 0; i < 6; i++) {
            System.out.println(i);
        }
        synchronized (this) {
            done = true;
            notify();
        }
    }
}

【讨论】:

    【解决方案2】:

    问题是,你没有调用你的m 方法,所以notify 永远不会被调用,所以你的线程永远休眠。你可以在main 中调用它,在开始之后,使用s.m()

    MyThread s = new MyThread();
    s.start();
    s.m();
    

    也许您应该在调用m 方法之前先sleep 一段时间,因为它可能比线程中的k 运行得更快:

    s.start();
    try {
        Thread.sleep(200);
    } catch (InterruptedException e) {
        // nothing to do
    }
    s.m();
    

    与问题关系不大,但main 中的throws 声明不是很可取,即使是生成的printStackTrace 也比扔掉异常要好。

    【讨论】:

    • 我应该在哪里称呼它?
    • 现在答案是“0 1 2 3 4 5,等待之前”为什么?
    • 看来,m 方法的调用时间比k 早。如果你的代码和我写的一样,也许你需要先睡一会儿再调用m
    • 为什么会这样?更令人惊讶的是,当我调试它时,输出不同:“等待之前,0 1 2 3 4 5,等待之后”有什么区别?! @meskobalazs
    • 我猜是因为当你调用m 时,线程可能已经启动了。请记住,k 在另一个线程中运行,m 在主线程中运行。更新了我的答案以提及这一点。
    猜你喜欢
    • 2012-12-21
    • 2020-06-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多