【问题标题】:Can Synchronized Methods Have Race Conditions? [Example]同步方法可以有竞争条件吗? [例子]
【发布时间】:2022-03-24 17:51:30
【问题描述】:

我正在学习同步方法,以防止 Java 中的竞争条件和不良行为。我看到了以下示例,并告诉我竞态条件非常微妙:

public class Messages {
  private String message = null;
  private int count = 2;

  // invariant 0 <= count && count <= 2

  public synchronized void put(String message) {
    while( count < 2 )
      this.wait();
    this.message = message;
    this.count = 0; 
    this.notifyAll(); 
  }

  public synchronized String getMessage() {
    while( this.count == 2 )
      this.wait();
    String result = this.message;
    this.count += 1;
    this.notifyAll();
    return result; 
  }
}

无论微妙与否,我认为我对同步方法的作用存在根本性的误解。我的印象是他们通过使用锁定令牌(或类似的)来限制对线程的访问,因此永远不能竞争。那么,如果它的方法是同步的,这个例子怎么会有竞争条件呢?谁能帮忙澄清一下?

【问题讨论】:

  • 我总是很想对这样的问题盲目地回答“是”(仅基于标题)。

标签: java multithreading race-condition


【解决方案1】:

我推测作者的想法是,由于 count 从 0 变为 2,因此两个线程可能会依次调用 put(),因此读者线程会错过其中一条消息。

这确实是一个竞态条件:阅读器和推杆争夺同一个锁,而被阅读的消息是否取决于notifyAll() 通知的线程。

【讨论】:

  • 您能否详细说明破坏 OP 示例的交错?我一直在尝试找到一个,因为我很好奇但我无法找到。如果两个单独的线程调用put,那么第一个将设置count = 0,第二个将设置wait()
  • 嗯,你是对的。 @Rome_Leader 能否请您不接受此答案,以便我将其删除。对不起。
  • 请用示例代码多解释一下。我不明白竞态条件是如何实现的。
【解决方案2】:

想想计数可能变成 > 2...

该代码也有难闻的气味。什么是计数应该计数?为什么要增加它并把它重置?为什么不必要地使用“this”?如果我在项目中看到这样的代码,我会非常仔细查看...

【讨论】:

  • 作为一个独立的竞争条件示例,我认为它并不完美。我只是想分析一下。根据我对另一个答案的评论,我认为您的指针是有道理的,并且似乎与我现在的想法一致!谢谢!
【解决方案3】:

多线程是当你使用 new Thread(runnable).start();这将启动一个新线程并转到 run() 方法。可运行是任何实现可运行的类。或者为线程扩展一个同步方法,确保如果这些线程想要读取被同步方法改变的数据是可能的,否则它可能是不变的,或者更糟的是,一半改变了。

【讨论】:

    【解决方案4】:

    Java 的同步方法可以让您在两个方法之间实现互斥,这意味着您可以假设它们不会交错。

    但是,您仍然有竞争条件,因为您可以根据首先运行的方法获得不同的行为。

    正如 JB Nizet 在他的回答中所建议的,考虑这两种排序中的每一种会发生什么(假设它们在不同的线程中运行)。

    【讨论】:

    • 啊,我想我明白了。顺序很重要,因为 count 初始化为 2,getMessage 会先走。它的等待会释放锁,并且,当它试图获取消息时,消息永远不会被 put 加载?
    • 请用示例代码解释清楚一点。
    【解决方案5】:

    当两个实体竞争一个资源时,就会出现竞争条件,如果结果取决于顺序,这可能会导致不可预知的行为。当您使用 notifyAll() 时,所有线程都会被唤醒,它们会竞相获取它们正在等待的锁,并且无法确定下一个将执行哪个。

    【讨论】:

    • 请用小示例代码场景解释解决方案。
    【解决方案6】:

    如果代码可以按预期工作,我认为计数值 >2 不是问题。 由于 put() 和 getMessage() 方法都是同步的,因此不能同时调用这两个方法。所以如果一个线程调用getMessage()并且count值为2,另一个线程就不能调用put()方法设置count=0并通知等待线程。同步太多,导致死锁。所以while部分不应该同步,可以这样解决。

     public void put(String message) {
        while( count < 2 )
          this.wait();
       synchronzied(this){
        this.message = message;
        this.count = 0; 
        this.notifyAll(); 
     }
    
     }
    
      public String getMessage() {
        while( this.count == 2 )
          this.wait();
       
        synchronized(this){
        String result = this.message;
        this.count += 1;
        this.notifyAll();
        
     }
    return result;
     }
    

    【讨论】:

      猜你喜欢
      • 2018-08-21
      • 1970-01-01
      • 2010-11-27
      • 2021-05-12
      • 1970-01-01
      • 1970-01-01
      • 2021-09-27
      • 1970-01-01
      • 2013-05-06
      相关资源
      最近更新 更多