在 if 条件下调用 Java notify()

Calling Java notify() within an if condition

我刚刚编写了一个简单的 java 示例来熟悉等待和通知方法的概念。

思路是在调用notify()时,主线程会打印出总和。

我的线程class

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

主要Class

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。

非常感谢任何帮助。

同步块意味着互斥。在任何给定时刻,只允许一个线程持有锁并执行同步块中的代码。此规则遍及由同一把锁保护的所有块。

在你的例子中,有两个这样的块使用相同的锁,所以它要么是主线程,要么是 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 可变,否则 JVM 无法保证您会在主线程中看到它的实际值。

而且您还应该明白,这种方法并不能保证您的主线程打印 5。可能会发生这样的情况,即在到达 println 调用时,第二个线程会打印一个或两个甚至更多的迭代,你会看到大于 5 的东西(尽管由于每次迭代都会调用 sleep() 而非常不幸)。