如何使用多个线程的wait和notify协议

具体来说,有人可以告诉我这段代码有什么问题。 它应该启动线程,所以应该打印“输入线程…”5次,然后等到调用notifyAll()。 但是,它会随机打印“Entering ..”和“Done ..”,并且仍在等待其他人。

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) { Runnable tc; Thread t; for(int i=0;i<5;i++) { tc = new ThreadTest(i); t = new Thread(tc); t.start(); } synchronized (obj) { obj.notifyAll(); } } } 

你没有对方法调用做任何明显的错误,但是你有一个竞争条件 。

虽然在理想的世界中,主线程将在所有工作线程到达wait()调用后到达其同步块, 但不能保证 (您明确告诉虚拟机您不希望线程按顺序执行)用主线程制作线程)。 可能会发生(例如,如果您只有一个核心)线程调度程序决定立即阻止所有工作线程,它们开始允许主线程继续。 可能由于高速缓存未命中而将工作线程切换出上下文。 可能是一个工作线程阻塞I / O(print语句)并且主线程在其位置切换。

因此,如果主线程在所有工作线程到达wait()调用之前设法到达同步块,则那些尚未到达wait()调用的工作线程将无法按预期运行。 由于当前设置不允许您控制此操作,因此必须添加对此的显式处理。 您可以添加某种变量,当每个工作线程到达wait()并且主线程不调用notifyAll()直到此变量达到5,或者您可以让主线程循环并重复调用notifyAll()时,会增加某种变量。以便工作线程在多个组中发布。

看一下java.util.concurrent包 – 有几个锁类提供比基本同步锁更强大的function – 一如既往,Java可以让你免于重新发明轮子。 CountDownLatch似乎特别相关。

总之,并发很难 。 您必须设计以确保当线程在您不想要的订单中执行时,一切仍然有效,以及您希望的订单。

我是CountDownLatch推荐的第二个。 这是我用于multithreading测试的模式:

 final int threadCount = 200; final CountDownLatch startPistol = new CountDownLatch(1); final CountDownLatch startingLine = new CountDownLatch(threadCount); final CountDownLatch finishingLine = new CountDownLatch(threadCount); // Do a business method... Runnable r = new Runnable() { public void run() { startingLine.countDown(); try { startPistol.await(); // TODO: challenge the multithreadedness here } catch (InterruptedException e) { Thread.interrupted(); } finishingLine.countDown(); } }; // -- READY -- for (int i = 0; i < threadCount; i++) { Thread t = new Thread(r); t.start(); } // Wait for the beans to reach the finish line startingLine.await(1000, TimeUnit.MILLISECONDS); // -- SET -- // TODO Assert no one has started yet // -- GO -- startPistol.countDown(); // go assertTrue(finishingLine.await(5000, TimeUnit.MILLISECONDS)); // -- DONE -- // TODO: final assert 

我们的想法是保证线程将执行的下一行代码是挑战multithreading或尽可能接近它的代码。 我认为线程是赛道上的跑步者。 你让他们在赛道上(创造/开始),等待他们完美地排队在起跑线上(每个人都叫做startingLine.countDown()),然后解雇起始手枪(startPistol.countDown())并等待所有人越过终点线(finishingLine.countDown())。

[编辑]应该注意的是,如果你没有想要在startingLine.await()和startingPistol.countDown()之间执行任何代码或检查,你可以将startingLine和startingPistol组合成一个CyclicBarrier(threadCount + 1)。 双重CountDownLatch方法实际上是相同的,并且允许主测试线程在所有其他线程排队之后以及在它们开始运行之前进行任何设置/检查,如果需要的话。

您的代码的根本问题是,在主线程调用notifyAll 之后 ,某些线程才会wait 。 所以当他们等待时,没有什么能唤醒他们。

要使其工作(使用wait / notify),您需要同步主线程,以便它等待所有子线程进入可以在进行该调用之前接收通知的状态。

一般来说,与waitnotify和原始锁同步是很棘手的。 在大多数情况下,通过使用Java并发实用程序类,您将获得更好的结果(即更简单,更可靠和更高效的代码)。