这个例子中是否存在竞争条件?如果是这样,怎么可以避免呢?

时间:2015-10-20 19:10:08

标签: java multithreading synchronization race-condition synchronized

我正在查看一些通知/等待示例并遇到了这个问题。我理解同步块本质上定义了一个关键部分,但是这不是一个竞争条件吗? Nothing指定首先输入哪个同步块。

public class ThreadA {
    public static void main(String[] args){
        ThreadB b = new ThreadB();
        b.start();

        synchronized(b){
            try{
                System.out.println("Waiting for b to complete...");
                b.wait();
            }catch(InterruptedException e){
                e.printStackTrace();
            }

        System.out.println("Total is: " + b.total);
        }
    }
}

class ThreadB extends Thread {
    int total;

    @Override
    public void run(){
        synchronized(this){
            for(int i=0; i<100 ; i++){
                total += i;
            }
            notify();
        }
    }
}

每个网站的输出:

  

等待b完成......

     

总计:4950

3 个答案:

答案 0 :(得分:5)

是的,它不能保证首先执行哪个线程。线程b可以在主线程开始等待之前进行通知。

除此之外,线程可以在没有得到通知的情况下从等待返回,因此在进入等待之前设置标志并检查它在技术上是不够的。您可以将其重写为类似

的内容
public class ThreadA {
    public static void main(String[] args) throws InterruptedException {
        ThreadB b = new ThreadB();
        b.start();

        synchronized(b){
            while (!b.isDone()) {
                System.out.println("Waiting for b to complete...");
                b.wait();
            }
            System.out.println("Total is: " + b.total);
        }
    }
}

class ThreadB extends Thread {
    int total;
    private boolean done = false;

    @Override
    public void run(){
        synchronized(this){
            for(int i=0; i<100 ; i++){
                total += i;
            }
            done = true;
            notify();
        }
    }

    public boolean isDone() {return done;}
}

这样主线程将等到b完成计算,无论谁先开始。

顺便说一句,API文档建议您不要在线程上进行同步。 JDK在线程上同步以实现Thread#join。终止的线程发送notifyAll,它接收到的任何东西都会收到。如果您从已获得锁定的线程中调用notify或notifyAll,则可以提前返回。这里的一个副作用是,如果你删除通知代码以相同的方式工作。

答案 1 :(得分:2)

是的,这是竞争条件。没有什么能阻止ThreadB启动,进入其run方法,并在ThreadA进入同步块之前自行同步(从而无限期地等待)。但是,考虑到新线程开始执行所需的时间,它不太可能发生。

处理此类情况的最简单,最推荐的方法是不编写自己的实现,而是选择使用Executor提供的可调用/未来。

要在不遵循标准的情况下修复此特定情况:

  • 设置布尔值&#39;已完成&#39;在ThreadB的同步块结束时设置的值。
  • 如果布尔值已完成&#39;进入同步块后为true,则不应调用wait。

答案 2 :(得分:2)

是 - 这是一个关于哪个线程首先进入哪个同步块的竞赛。对于比赛的大多数情况,输出和答案将是相同的。但是,对于其中一个,该程序将陷入僵局:

  1. 主要开始呼叫b.start()并立即安排出去。
  2. 线程B启动,进入同步,调用notify()。
  3. Main进入同步块,调用wait()
  4. 在这种情况下,main将永远等待,因为线程b在wait()之前被阻塞时调用notify。

    尽管如此,这是不太可能的 - 但是通过所有线程,你应该得出结论,它会发生,然后是在最糟糕的时候。