编程语言
首页 > 编程语言> > java-实现Runnable而不是扩展Thread时的不同行为

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

作者:互联网

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

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

到目前为止,没有什么特别的.但是,当您执行Runnable实现而不是扩展Thread类时,执行此小程序时,您的线程很有可能会被阻塞“等待计算…”.

如果我们扩展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;
    }
}

解决方法:

至少在实现Runnable版本中,您没有做任何事情来确保ReadCalculation线程在Calculator线程进入其同步块之前到达wait().如果Calculator线程首先进入其同步块,则它将在ReadCalculation线程调用wait()之前调用notifyAll().如果发生这种情况,那么notifyAll()是一个空操作,并且ReadCalculation线程将永远等待. (这是因为notifyAll()仅关心已经在对象上等待的线程;它没有在对象上设置任何类型的指示符,该指示符可以被后续对wait()的调用检测到.)

要解决此问题,您可以向Calculator添加一个属性,该属性可用于检查是否已完成,并且仅在未完成Calculator时才调用wait():

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

(请注意,为了完全避免争用情况,将整个if语句置于同步块内部是非常重要的,并且Calculator将此属性设置在调用notifyAll()的同步块内部.您知道为什么吗?)

(顺便说一句,彼得·劳里(Peter Lawrey)的评论“一个线程可以在其他线程甚至没有开始之前就轻松地进行100次迭代”的说法极具误导性,因为在您的程序中,所有100次迭代都发生在计算器进入其同步块之后.自ReadCalculation阻止线程进入其同步块并在Calculator处于其同步块中时调用calc.wait(),这是1次迭代,100次迭代还是1,000,000次迭代无关紧要,除非它具有有趣的优化效果并且可以更改在此之前的节目时间.)

您尚未发布完整的Extended Thread版本,但是如果我正确理解它的外观,那么它实际上仍然具有相同的竞争条件.但是,种族条件的本质是微小的变化会极大地影响不当行为的可能性.即使似乎从未真正发生过错误,您仍然需要修复竞争条件,因为几乎可以肯定的是,如果您运行该程序足够的时间,偶尔会出现错误.

对于为什么一种方式比另一种方式更容易发生不当行为,我没有很好的解释.但是,正如上面的user1643723所评论的那样,扩展线程方法意味着您以外的许多代码也可能会锁定您的Calculator实例;这很可能会产生某种效果.但老实说,我不值得过多担心种族状况可能会或多或少地引起不良行为的原因.无论故事结束如何,我们都必须修复它.

偶然:

>以上,我使用了if(!calc.isCalculationDone());但是实际上最好的做法是始终将对wait()的调用包装在适当的while循环中,因此实际上您应该编写while(!calc.isCalculationDone()).这样做的主要原因有两个:

>在非平凡的程序中,您不一定知道为什么调用notifyAll(),或者即使您这样做,也不知道在等待线程实际唤醒并重新获得同步的线程时,该原因是否仍然适用.锁.如果使用while(not_ready_to_proceed()){wait();,则可以更轻松地推断出notify()/ wait()交互的正确性. }结构来表达wait_until_ready_to_proceed()的想法,而不是仅仅编写wait()并尝试确保没有任何东西会导致它在我们还没准备好时返回.
>在某些操作系统上,向进程发送信号将唤醒所有正在等待的线程.这称为伪唤醒.有关更多信息,请参见“Do spurious wakeups actually happen?”.因此,即使没有其他称为notify()或notifyAll()的线程,一个线程也可能被唤醒.

> Calculator.run()中的for循环不应位于同步块中,因为它不需要任何同步,因此不需要争用.在您的小型程序中,它实际上没有任何区别(因为其他线程在那一点上实际上都没有任何关系),但是最佳实践始终是尽量减少同步块内的代码量.

标签:multithreading,runnable,race-condition,synchronized,java
来源: https://codeday.me/bug/20191026/1940116.html