在if条件语句中调用Java的notify()方法

4

我刚刚写了一个简单的Java示例,以熟悉等待和通知方法的概念。

这个想法是,在调用notify()时,主线程将打印总和。

MyThread类

public class MyThread extends Thread {
    public int times = 0;

    @Override
    public void run() {

        synchronized (this) {
            try {
                for (int i = 0; i < 10; i++) {
                    times += 1;
                    Thread.sleep(500);
                    if (i == 5) {
                        this.notify();
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

主类

public class Main {

    public static void main(String[] args) {

        MyThread t = new MyThread();
        synchronized (t) {
            t.start();
            try {
                t.wait();
            } catch (Exception e) {
                e.printStackTrace();
            }
            System.out.println(t.times);
        }
    }
}

预期结果

期望结果是5,但实际得到的却是10。

我原本认为当调用notify()时,主线程会被唤醒并执行System.out.println(t.times)语句,这应该输出5。然后run()方法将继续运行直到完成for循环,这将把times的值更新为10。

非常感谢您的帮助。


5
notify不会释放锁。 - tkausl
你能否详细解释一下这个问题? - Mazen
2
可能是A simple scenario using wait() and notify() in java的重复问题。 - Markus Mitterauer
线程不会离开同步块。因此,即使wait返回,主线程也无法继续运行。 - Fildor
谢谢大家。明白了。嗯,在这种情况下我可以用join()来代替。 - Mazen
1个回答

5
同步块意味着互斥。在任何时刻,只允许一个线程持有锁并执行同步块内的代码。这个规则适用于所有由同一把锁保护的块。
在您的情况下,有两个使用相同锁的这样的块,因此要么是主线程或MyThread被允许在这些块中执行代码,另一个线程必须等待。因此,您在这里有以下场景:
1. 主线程获取锁。 2. 主线程启动第二个线程。 3. 第二个线程进入同步块,但不能进入,因为锁被主线程持有。 4. 主线程调用wait()。这个调用释放锁并将主线程置于WAITING状态。 5. 现在第二个线程可以获取锁并进入同步块。 6. 第二个线程计数到五并调用notify()。这个调用不会释放锁,它只是通知主线程它可以继续执行,只要它能重新获取锁。 7. 主线程醒来,但它不能继续执行,因为它不能重新获取锁(它仍然被第二个线程持有)。请记住,在由同一把锁保护的同步块中,不能有两个线程同时活动,现在第二个线程仍然处于活动状态,因此主线程必须继续等待。 8. 第二个线程继续计数,将times设置为10并最终离开同步块,释放锁。 9. 主线程重新获取锁,并可以继续执行println。但到这时,times已经是10了。
使用join()也无济于事,因为结果是相同的——只有当第二个线程完成后,主线程才能继续执行。
如果您希望主线程在第二个线程达到5时立即继续执行,您需要获取锁并在该事件之后立即释放它:
public class MyThread extends Thread {
    public volatile int times = 0;

    @Override
    public void run() {
        try {
            for (int i = 0; i < 10; i++) {
                times += 1;
                Thread.sleep(500);
                if (i == 5) {
                    synchronized(this) {
                        this.notify();
                    }
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

不要忘记将“times”声明为volatile,否则JVM不保证您在主线程中看到其实际值。
您还应该理解,这种方法不能保证主线程打印5。可能发生的是,当它到达“println”调用时,第二个线程进行了一次或两次甚至更多次迭代,您会看到大于5的某些值(尽管由于每次迭代上的“sleep()”调用非常不幸)。

它运行得非常好。谢谢Andrew。非常感谢。 - Mazen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接