7

我正在看一些通知/等待的例子,并遇到这一个。我明白一个同步块基本上定义了一个关键部分,但是这不是一个竞争条件?没有指定首先输入哪个同步块。每个网站在这个例子中是否存在竞争条件?如果是这样,怎么可能避免?

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

+0

是的,理论上新线程可以在主线程调用wait()之前'notify()'。 – overthink

+1

不要将'synchronized'块视为_preventing_种族。它仅仅限制了比赛的胜利。这就像汽车竞赛场地的一部分,赛道太狭窄,汽车相互超车。他们仍在比赛中,但他们必须通过单节档案。 “同步”模块同样如此:线程不在这里竞赛__,但他们竞争_get here_,并且他们在其他地方跑。 –

+1

@ jameslarge这是一个惊人的比喻!同步块中不能同时存在两个线程,但除非明确指定,否则无法告诉谁可以先到达,是正确的? – trevalexandro

回答

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,它加入的任何东西都会收到。如果您打电话通知或通知所有您已获得锁定的线索,则加入的线索可能会提前返回。这里的一个副作用是,如果您删除通知,代码的作用方式相同。

+0

从ThreadB中删除'notify()',并且wait()仍然唤醒......:D – ZhongYu

+0

@ bayou.io:是的,因为线程在结束时发送通知。 –

+0

我正在看这个,理论上在主线程等待之前无法通知?我知道由于并发性,赔率很渺茫,但我很好奇,如果我正在考虑这个错误@NathanHughes – trevalexandro

2

是的,这是一个竞争条件。没有任何东西阻止ThreadB启动,输入其运行方法,并且在ThreadA进入其同步块之前自动进行同步(因此无限期地等待)。但是,考虑到新线程开始执行所花的时间,这种情况不太可能发生。

处理这种情况最简单也是最推荐的方式是不要编写自己的实现,而是选择使用由Executor提供的可调用/未来版本。

要解决这种特定情况下,而不必遵循标准:

  • 设置设定在ThreadB的同步块的末尾的布尔“完成”的值。
  • 如果输入同步块后布尔'已完成'为true,则不应该调用wait。
2

是 - 这是关于哪个线程首先进入哪个同步块的竞赛。对于比赛的大多数情况,输出和答案都是一样的。然而,对于其中一个,程序将会出现死锁:

  1. 主要启动调用b.start()并立即调度。
  2. 线程B启动,进入同步,调用notify()。
  3. 主要进入其同步块,调用wait()

在这种情况下,主要将永远等待自称为线程B通知之前主阻塞等待()。

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

相关问题