[英]Different behavior when implementing Runnable instead of extending Thread
So here's the code. 所以这是代码。 Basically if we change the ReadCalculation and Calculator classes to extend Thread instead of implementing Runnable we would need to instantiate these classes and pass them to a new thread object or just call start() on them.
基本上,如果我们更改ReadCalculation和Calculator类来扩展Thread而不是实现Runnable,我们需要实例化这些类并将它们传递给新的线程对象或者只是调用它们的start()。
Calculator calc = new Calculator();
new ReadCalculation(calc).start();
new ReadCalculation(calc).start();
calc.start();
Nothing special so far.. But when you execute this tiny program, there's a huge chance that your threads will stay blocked "Waiting for calculation..." if we're going over the Runnable implementation over extending the Thread class. 到目前为止没什么特别的。但是当你执行这个小程序时,如果我们通过扩展Thread类来查看Runnable实现,那么你的线程很可能会被阻止“等待计算...”。
If we're extending the Thread class instead of implementing Runnable the behavior is correct without any sign of race condition. 如果我们扩展Thread类而不是实现Runnable,则行为是正确的,没有任何竞争条件的迹象。 Any ideas on which could be the source of this behavior?
任何想法可能是这种行为的来源?
public class NotifyAllAndWait {
public static void main(String[] args) {
Calculator calc = new Calculator();
Thread th01 = new Thread(new ReadCalculation(calc));
th01.start();
Thread th02 = new Thread(new ReadCalculation(calc));
th02.start();
Thread calcThread = new Thread(calc);
calcThread.start();
}
}
class ReadCalculation implements Runnable {
private Calculator calc = null;
ReadCalculation(Calculator calc) {
this.calc = calc;
}
@Override
public void run() {
synchronized (calc) {
try {
System.out.println(Thread.currentThread().getName() + " Waiting for calculation...");
calc.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " Total: " + calc.getTotal());
}
}
}
class Calculator implements Runnable {
private int total = 0;
@Override
public void run() {
synchronized(this) {
System.out.println(Thread.currentThread().getName() + " RUNNING CALCULATION!");
for(int i = 0; i < 100; i = i + 2){
total = total + i;
}
notifyAll();
}
}
public int getTotal() {
return total;
}
}
When you perform a wait()
this needs to be in a loop after a state change you performed the notify()
block. 执行
wait()
,在执行notify()
块的状态更改后,这需要处于循环中。 eg 例如
// when notify
changed = true;
x.notifyAll();
// when waiting
while(!changed)
x.wait();
If you don't do this you will run into issue such as wait() waking spuriously or notify() being lost. 如果你不这样做,你将遇到问题,例如wait()虚假地醒来或通知()丢失。
Note: a thread can easily to your 100 iterations before the other threads have even started. 注意:在其他线程开始之前,线程可以轻松地进行100次迭代。 It is possible that pre-creating the
Thread
object makes enough of a difference to the performance to change the outcome in your case. 预先创建
Thread
对象可能会对性能产生足够的差异,从而改变您的案例中的结果。
In the implements Runnable
version, at least, you do nothing to ensure that the ReadCalculation
threads reach the wait()
before the Calculator
thread enters its synchronized
block. 在
implements Runnable
版本中,至少,您无需确保ReadCalculation
线程在Calculator
线程进入其synchronized
块之前到达wait()
。 If the Calculator
thread enters its synchronized
block first, then it will call notifyAll()
before the ReadCalculation
threads have called wait()
. 如果
Calculator
线程首先进入其synchronized
块,那么它将在ReadCalculation
线程调用wait()
之前调用notifyAll()
wait()
。 And if that happens, then the notifyAll()
is a no-op, and the ReadCalculation
threads will wait forever. 如果发生这种情况,那么
notifyAll()
是一个无操作,而ReadCalculation
线程将永远等待。 (This is because notifyAll()
only cares about threads that are already waiting on an object; it does not set any sort of indicator on the object that could be detected by subsequent calls to wait()
.) (这是因为
notifyAll()
只关心那些在对象上已经等待的线程,它不设置任何类型的对象指标,可以通过后续调用进行检测,以wait()
To fix that, you could add a property to Calculator
that can be used to check for done-ness, and only call wait()
if the Calculator
is not done: 要解决这个问题,您可以向
Calculator
添加一个可用于检查完成情况的属性,并且只有在未完成Calculator
时才调用wait()
:
if(! calc.isCalculationDone()) {
calc.wait();
}
(Note that, in order to completely avoid the race condition, it's important that the entire if
-statement be inside the synchronized
block, and that Calculator
set this property inside the synchronized
block that calls notifyAll()
. Do you see why?) (注意,为了完全避免竞争条件,重要的是整个
if
-statement 在 synchronized
块内,并且Calculator
在调用notifyAll()
的synchronized
块内设置此属性。你明白为什么吗?)
(By the way, Peter Lawrey's comment that "a thread can easily to your 100 iterations before the other threads have even started" is highly misleading, since in your program the 100 iterations all happen after Calculator
has entered its synchronized
block. Since the ReadCalculation
threads are blocked from entering their synchronized
blocks and calling calc.wait()
while Calculator
is in its synchronized
block, it shouldn't matter whether this is 1 iteration, 100 iterations, or 1,000,000 iterations, unless it has interesting optimization effects that could change the timing of the program before that point.) (顺便说一句,Peter Lawrey的评论“在其他线程开始之前,线程可以很容易地进行100次迭代”是非常误导的,因为在你的程序中,100次迭代都发生在
Calculator
进入其synchronized
块之后。自ReadCalculation
如果Calculator
处于synchronized
块中,则阻止线程进入其synchronized
块并调用calc.wait()
,无论这是1次迭代,100次迭代还是1,000,000次迭代,都应该无关紧要,除非它有可能改变的有趣的优化效果该点之前的计划时间。)
You haven't posted the entire extends Thread
version, but if I understand correctly what it looks like, then it actually still has the same race condition. 你没有发布整个
extends Thread
版本,但如果我理解它的外观,那么它实际上仍然具有相同的竞争条件。 However, it's in the nature of race conditions that minor changes can massively affect the likelihood of misbehavior. 然而,在竞争条件的本质中,微小的变化会严重影响不良行为的可能性。 You still need to fix the race condition even if it never seems to actually misbehave, because it's almost certain that it will misbehave occasionally if you run the program enough times.
你仍然需要修复竞争条件,即使它似乎从来没有真正行为不端,因为几乎可以肯定,如果你运行程序足够多,它会偶尔出现异常。
I don't have a good explanation for why the misbehavior seems to happen much more often with one approach than the other; 我没有一个很好的解释,为什么这种不良行为似乎比一种方法更频繁地发生; but as user1643723 comments above, the
extends Thread
approach implies that lots of code other than yours is also likely to lock on your Calculator
instance; 但正如上面user1643723意见,
extends Thread
方法意味着大量的比你的其它代码也有可能锁定您的Calculator
实例; and this could well have an effect of some sort. 这很可能会产生某种影响。 But honestly, I don't think it's worth worrying too much about the reasons why a race condition might cause a misbehavior more often or less often;
但老实说,我认为值得担心的原因是竞争条件可能会更频繁或更不频繁地导致不良行为; we have to fix it regardless, so, end of story.
我们必须解决它,所以,故事结束。
Incidentally: 偶然:
Above, I used if(! calc.isCalculationDone())
; 上面,我用
if(! calc.isCalculationDone())
; but it's actually a best practice to always wrap calls to wait()
in an appropriate while
-loop, so really you should write while(! calc.isCalculationDone())
. 但实际上最好的做法是始终在适当的
while
循环中包含对wait()
调用,所以你应该写while(! calc.isCalculationDone())
。 There are two major reasons for this: 这有两个主要原因:
In nontrivial programs, you don't necessarily know why notifyAll()
was called, or even if you do, you don't know whether that reason is still applicable by the time the waiting thread has actually woken up and regained the synchronized
-lock. 在非平凡的程序中,您不一定知道为什么调用
notifyAll()
,或者即使您这样做,也不知道在等待线程实际唤醒并重新获得synchronized
锁定时该原因是否仍然适用。 It makes it much easier to reason about the correctness of your notify()
/ wait()
interactions if you use the while(not_ready_to_proceed()) { wait(); }
如果你使用
while(not_ready_to_proceed()) { wait(); }
它可以更容易地while(not_ready_to_proceed()) { wait(); }
你的notify()
/ wait()
交互的正确性while(not_ready_to_proceed()) { wait(); }
while(not_ready_to_proceed()) { wait(); }
structure to express the idea of wait_until_ready_to_proceed()
, rather than just writing wait()
and trying to make sure that nothing will ever cause it to return while we're not ready. while(not_ready_to_proceed()) { wait(); }
结构来表达wait_until_ready_to_proceed()
的想法,而不是仅仅编写wait()
并尝试确保在我们还没有准备好的时候什么都不会导致它返回。
On some operating systems, sending a signal to the process will wake up all threads that are wait()
-ing. 在某些操作系统上,向进程发送信号将唤醒所有处于
wait()
ing的线程。 This is called a spurious wakeup ; 这被称为虚假唤醒 ; see "Do spurious wakeups actually happen?"
看到“真的发生了虚假的唤醒吗?” for more information.
欲获得更多信息。 So a thread may get woken up even if no other thread called
notify()
or notifyAll()
. 因此,即使没有其他线程调用
notify()
或notifyAll()
线程也可能被唤醒。
The for
-loop in Calculator.run()
shouldn't be in the synchronized
block, because it doesn't require any synchronization, so the contention is not needed. Calculator.run()
的for
-loop不应该在synchronized
块中,因为它不需要任何同步,因此不需要争用。 In your small program, it doesn't actually make a difference (since none of the other threads actually has anything to do at that point anyway), but the best practice is always to try to minimize the amount of code inside synchronized
blocks. 在你的小程序中,它实际上并没有什么区别(因为其他任何线程实际上都没有任何事情要做),但最好的做法是尽量减少
synchronized
块内的代码量。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.