繁体   English   中英

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

[英]Different behavior when implementing Runnable instead of extending Thread

所以这是代码。 基本上,如果我们更改ReadCalculation和Calculator类来扩展Thread而不是实现Runnable,我们需要实例化这些类并将它们传递给新的线程对象或者只是调用它们的start()。

Calculator calc = new Calculator();
new ReadCalculation(calc).start();
new ReadCalculation(calc).start();
calc.start();

到目前为止没什么特别的。但是当你执行这个小程序时,如果我们通过扩展Thread类来查看Runnable实现,那么你的线程很可能会被阻止“等待计算...”。

如果我们扩展Thread类而不是实现Runnable,则行为是正确的,没有任何竞争条件的迹象。 任何想法可能是这种行为的来源?

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;
    }
}

执行wait() ,在执行notify()块的状态更改后,这需要处于循环中。 例如

// when notify
changed = true;
x.notifyAll();

// when waiting
while(!changed)
    x.wait();

如果你不这样做,你将遇到问题,例如wait()虚假地醒来或通知()丢失。

注意:在其他线程开始之前,线程可以轻松地进行100次迭代。 预先创建Thread对象可能会对性能产生足够的差异,从而改变您的案例中的结果。

implements Runnable版本中,至少,您无需确保ReadCalculation线程在Calculator线程进入其synchronized块之前到达wait() 如果Calculator线程首先进入其synchronized块,那么它将在ReadCalculation线程调用wait()之前调用notifyAll() wait() 如果发生这种情况,那么notifyAll()是一个无操作,而ReadCalculation线程将永远等待。 (这是因为notifyAll()只关心那些在对象上已经等待的线程,它设置任何类型的对象指标,可以通过后续调用进行检测,以wait()

要解决这个问题,您可以向Calculator添加一个可用于检查完成情况的属性,并且只有在完成Calculator时才调用wait()

if(! calc.isCalculationDone()) {
    calc.wait();
}

(注意,为了完全避免竞争条件,重要的是整个if -statement synchronized块内,并且Calculator 调用notifyAll()synchronized设置此属性。你明白为什么吗?)

(顺便说一句,Peter Lawrey的评论“在其他线程开始之前,线程可以很容易地进行100次迭代”是非常误导的,因为在你的程序中,100次迭代都发生 Calculator进入其synchronized块之后。自ReadCalculation如果Calculator处于synchronized块中,则阻止线程进入其synchronized块并调用calc.wait() ,无论这是1次迭代,100次迭代还是1,000,000次迭代,都应该无关紧要,除非它有可能改变的有趣的优化效果该点之前的计划时间。)


你没有发布整个extends Thread版本,但如果我理解它的外观,那么它实际上仍然具有相同的竞争条件。 然而,在竞争条件的本质中,微小的变化会严重影响不良行为的可能性。 你仍然需要修复竞争条件,即使它似乎从来没有真正行为不端,因为几乎可以肯定,如果你运行程序足够多,它会偶尔出现异常。

我没有一个很好的解释,为什么这种不良行为似乎比一种方法更频繁地发生; 但正如上面user1643723意见, extends Thread方法意味着大量的比你的其它代码也有可能锁定您的Calculator实例; 这很可能会产生某种影响。 但老实说,我认为值得担心的原因是竞争条件可能会更频繁或更不频繁地导致不良行为; 我们必须解决它,所以,故事结束。


偶然:

  • 上面,我用if(! calc.isCalculationDone()) ; 但实际上最好的做法是始终在适当的while循环中包含对wait()调用,所以你应该写while(! calc.isCalculationDone()) 这有两个主要原因:

    • 在非平凡的程序中,您不一定知道为什么调用notifyAll() ,或者即使您这样做,也不知道在等待线程实际唤醒并重新获得synchronized锁定时该原因是否仍然适用。 如果你使用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(); }结构来表达wait_until_ready_to_proceed()的想法,而不是仅仅编写wait()并尝试确保在我们还没有准备好的时候什么都不会导致它返回。

    • 在某些操作系统上,向进程发送信号将唤醒所有处于wait() ing的线程。 这被称为虚假唤醒 ; 看到“真的发生了虚假的唤醒吗?” 欲获得更多信息。 因此,即使没有其他线程调用notify()notifyAll()线程也可能被唤醒。

  • Calculator.run()for -loop不应该在synchronized块中,因为它不需要任何同步,因此不需要争用。 在你的小程序中,它实际上并没有什么区别(因为其他任何线程实际上都没有任何事情要做),但最好的做法是尽量减少synchronized块内的代码量。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM