How to use wait and notify protocol with multiple threads
具体来说,谁能告诉我这段代码有什么问题。它应该启动线程,所以应该打印”进入线程..” 5 次,然后等到 notifyAll() 被调用。但是,它会随机打印”Entering..”和”Done..”,并且仍然在等待其他人。
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
public class ThreadTest implements Runnable {
private int num; private static Object obj = new Object(); ThreadTest(int n) { num=n; } @Override public void run() { synchronized (obj) { try { System.out.println(“Entering thread”+num); obj.wait(); System.out.println(“Done Thread”+num); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { |
您没有对方法调用做任何明显错误的事情,但您有一个竞争条件。
虽然在理想世界中,主线程将在所有工作线程到达 wait() 调用后到达其同步块,但不能保证这一点(您明确告诉虚拟机您不想要线程通过使它们成为线程来与主线程按顺序执行)。可能会发生(例如,如果您只有一个内核)线程调度程序决定立即阻止所有工作线程,它们开始允许主线程继续。可能是由于缓存未命中,工作线程被上下文切换了。可能是一个工作线程阻塞了 I/O(打印语句)并且主线程被切换到它的位置。
因此,如果主线程在所有工作线程到达 wait() 调用之前设法到达同步块,则那些尚未到达 wait() 调用的工作线程将无法按预期运行。由于当前设置不允许您对此进行控制,因此您必须添加对此的显式处理。您可以添加某种变量,该变量在每个工作线程到达 wait() 时递增,并让主线程在此变量达到 5 之前不调用 notifyAll(),或者您可以让主线程循环并重复调用 notifyAll(),以便工作线程在多个组中释放。
看看 java.util.concurrent 包 – 有几个锁类提供了比基本同步锁更强大的功能 – 与以往一样,Java 使您免于重新发明轮子。 CountDownLatch 似乎特别相关。
总之,并发是很难的。您必须进行设计以确保当线程以您不想要的顺序以及您想要的顺序执行时一切仍然有效。
- 感谢斯科特的回答。我想一个简单的解决方法是将 Thread.sleep(1000) 放在 notifyAll() 之前,以确保所有线程都进入了 wait() 状态
- 这可能行得通,但它有点幼稚的解决方案。如果您想在等待调用之前做更多的工作,是否有必要尽快释放工作线程,或者即使工作线程花费的时间比您估计的要长,您也会遇到同样的问题。您必须使用锁定。这就是并发编程的全部意义所在。看看 David Blevin 的代码:这是一个如何使用倒计时锁的绝佳示例。
我赞同 CountDownLatch 的建议。这是我用于多线程测试的模式:
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
final int threadCount = 200;
final CountDownLatch startPistol = new CountDownLatch(1); // Do a business method… // TODO: challenge the multithreadedness here } catch (InterruptedException e) { // — READY — for (int i = 0; i < threadCount; i++) { // Wait for the beans to reach the finish line // — SET — // TODO Assert no one has started yet // — GO — startPistol.countDown(); // go assertTrue(finishingLine.await(5000, TimeUnit.MILLISECONDS)); // — DONE — // TODO: final assert |
我们的想法是保证你的线程将执行的下一行代码是挑战多线程或尽可能接近多线程的代码。我认为线程是Rails上的跑步者。你让他们在赛道上(创建/开始),等待他们完美地排在起跑线上(每个人都调用了startingLine.countDown()),然后启动发令手枪(startPistol.countDown())并等待每个人越过终点线 (finishingLine.countDown())。
[EDIT] 应该注意的是,如果您没有要在startingLine.await() 和startingPistol.countDown() 之间执行的任何代码或检查,您可以将startingLine 和startingPistol 合并为一个CyclicBarrier(threadCount 1 )。双 CountDownLatch 方法实际上是相同的,并且允许主测试线程在所有其他线程排好之后并在它们开始运行之前进行任何设置/检查,如果需要的话。
你的代码的根本问题是一些线程直到主线程调用 notifyAll 之后才到达 wait。所以当他们等待时,没有什么能唤醒他们。
要完成这项工作(使用等待/通知),您需要同步主线程,以便它等待所有子线程进入可以在调用之前接收通知的状态。
一般来说,与 wait、notify 和原始锁进行同步是很棘手的。在大多数情况下,通过使用 Java 并发实用程序类,您将获得更好的结果(即更简单、更可靠和更高效的代码)。
来源:https://www.codenong.com/3376139/