实现Runnable而不是扩展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;
    }
}

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语句放在synchronized块中,并且Calculator在调用notifyAll()synchronized块内部设置此属性。你明白为什么?)

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


您尚未发布整个extends Thread版本,但如果我正确理解它的外观,那么它实际上仍然具有相同的竞争条件。 然而,根据竞争条件的性质,微小的变化可能会严重影响不正当行为的可能性。 即使它看起来从来没有出现过错,仍然需要修正竞争条件,因为几乎可以肯定,如果您运行该程序的次数足够多,它会偶尔会出现不正常行为。

我没有一个很好的解释,为什么这种不正当行为似乎更经常地以一种方式发生,而不是另一种; 但正如上面的user1643723注释, extends Thread方法意味着除您之外的大量代码也可能锁定您的Calculator实例; 这可能会有某种效果。 但说实话,我认为值得担忧的是,为什么竞争条件会更频繁地或不经常地引起不良行为; 无论如何,我们必须解决它,故事的结尾。


顺便:

  • 上面,我用if(! calc.isCalculationDone()) ; 但实际上最好的做法是始终在适当的while while(! calc.isCalculationDone())调用包装为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()线程。 这被称为虚假唤醒; 请参阅“真正发生虚假唤醒?” 了解更多信息。 因此,即使没有其他线程调用notify()notifyAll()线程也可能会被唤醒。

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


  • 当你执行一个wait()时,你需要在执行notify()模块的状态改变后进入一个循环。 例如

    // when notify
    changed = true;
    x.notifyAll();
    
    // when waiting
    while(!changed)
        x.wait();
    

    如果你不这样做,你将遇到诸如wait()虚假地唤醒或notify()丢失等问题。

    注意:在其他线程开始之前,线程可以很容易地进行100次迭代。 预先创建Thread对象可能会对性能产生不同影响,以改变您的案例中的结果。

    链接地址: http://www.djcxy.com/p/16391.html

    上一篇: Different behavior when implementing Runnable instead of extending Thread

    下一篇: Why does Thread implement Runnable?