这个例子中有竞争条件吗?如果是这样,如何避免?

Is there a race condition in this example? If so, how could it be avoided?

我正在查看一些 notify/wait 示例并发现了这个示例。我知道同步块本质上定义了一个关键部分,但这不是竞争条件吗?没有指定首先进入哪个同步块。

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

每个网站的输出:

Waiting for b to complete...

Total is: 4950

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

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

在没有遵循标准的情况下解决这个特殊情况:

  • 在 ThreadB 的同步块末尾设置一个布尔值 'finished'。
  • 如果布尔值'finished'在进入synchronized块后为真,那么你不应该调用wait。

是的——这是一场关于哪个线程先进入哪个同步块的竞赛。对于比赛的大多数场景,输出和答案都是相同的。然而,一方面,程序会死锁:

  1. Main 开始调用 b.start() 并立即安排出去。
  2. 线程B启动,进入synchronized,调用notify()。
  3. Main 进入同步块,调用 wait()

在这种情况下,main 将永远等待,因为线程 b 在 main 阻塞在 wait() 上之前调用了 notify。

也就是说,这不太可能 - 但对于所有线程,您应该得出结论,它会发生,然后在最糟糕的时间发生。

对,不能保证哪个线程先执行。线程 b 可以在主线程开始等待之前发出通知。

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

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,则加入它的东西可能 return 提前。这里的一个副作用是,如果您删除通知,代码将以相同的方式工作。