具体来说,有人可以告诉我这段代码有什么问题。它应该启动线程,所以应该打印“输入线程...”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();
}
}
}
答案 0 :(得分:8)
你没有对方法调用做任何明显错误,但你有一个race condition。
虽然在理想的世界中,主线程将在所有工作线程到达wait()调用后到达其synchronized块,无法保证(您明确告诉虚拟机您不希望线程通过使它们成为线程而与主线程一起执行。可能发生(例如,如果您只有一个核心)线程调度程序决定立即阻止所有工作线程,它们开始允许主线程继续。由于高速缓存未命中,工作线程可能是上下文切换出来的。可能是一个工作线程阻塞了I / O(print语句),并且主线程在其位置被切换。
因此,如果主线程在所有工作线程到达wait()调用之前设法到达同步块,则那些尚未到达wait()调用的工作线程将无法按预期运行。由于当前设置不允许您控制此操作,因此必须添加对此的显式处理。您可以添加某种变量,当每个工作线程到达wait()并且主线程不调用notifyAll()直到此变量达到5,或者您可以让主线程循环并重复调用notifyAll()时,会增加某种变量。这样工作线程就可以在多个组中发布。
看一下java.util.concurrent
包 - 有几个锁类提供比基本同步锁更强大的功能 - 一如既往,Java可以避免重新发明轮子。 CountDownLatch似乎特别相关。
总之,并发性是 hard 。您必须设计以确保线程在您不想要的订单中执行时所有内容仍然有效,以及您希望的订单。
答案 1 :(得分:2)
我是CountDownLatch推荐的第二个。这是我用于多线程测试的模式:
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
我们的想法是保证线程将执行的下一行代码是挑战多线程或尽可能接近它的代码。我认为线程是赛道上的跑步者。你让他们在赛道上(创造/开始),等待他们在起跑线上完美排队(每个人都叫做startingLine.countDown()),然后解雇起始手枪(startPistol.countDown())并等待所有人越过终点线(finishingLine.countDown())。
[编辑]应该注意的是,如果你没有想要在startingLine.await()和startingPistol.countDown()之间执行任何代码或检查,你可以将startingLine和startingPistol合并为一个CyclicBarrier(threadCount + 1)。双重CountDownLatch方法实际上是相同的,并且允许主测试线程在所有其他线程排成一行并且在它们开始运行之前进行任何设置/检查,如果需要的话。
答案 2 :(得分:0)
您的代码的根本问题是,在主线程调用wait
之后,某些线程无法到达notifyAll
,直到。所以当他们等待时,什么都不会唤醒他们。
要使这项工作(使用wait / notify),您需要同步主线程,以便它等待所有子线程进入可以在进行该调用之前接收通知的状态。
一般来说,与wait
,notify
和原始锁同步是很棘手的。在大多数情况下,通过使用Java并发实用程序类,您将获得更好的结果(即更简单,更可靠和更高效的代码)。