简体   繁体   English

实现Runnable而不是扩展Thread时的不同行为

[英]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.

相关问题 等待并通过实施Runnable和扩展线程示例进行通知 - wait and notify with implementing Runnable and extending Thread example 实现Runnable接口时导致死锁情况,扩展线程class时运行正常 - Causing Deadlock situation when implementing Runnable interface and running fine when extending Thread class 扩展Thread类的主要优点是什么(或何时扩展Thread而不是实现runnable) - What is the main advantage of extending Thread class (or when to extend Thread instead of implement runnable) HttpServlet 如何在不实现 Runnable 或扩展线程的情况下创建线程 - How HttpServlet can create threads without implementing Runnable or extending thread 性能明智,可以更快地实现Runnable或扩展Thread类 - Performance wise which is faster implementing Runnable or extending a Thread class 为什么在实现Runnable时使用Thread.currentThread()。isInterrupted()而不是Thread.interrupted()? - Why use Thread.currentThread().isInterrupted() instead of Thread.interrupted() when implementing Runnable? 在实现Runnable的Java中使用线程时形状未移动 - The shape is not moving when using thread in java implementing runnable 实现Runnable时,run()中的此引用可以引用Thread对象吗? - Can this reference in run() refer to Thread object when implementing Runnable? 当我在 java 中使用线程实现可运行时,椭圆没有移动 - The oval is not moving when I use thread in java implementing runnable Question 实现Runnable接口的线程的子类 - subclass of thread implementing Runnable interface
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM