【问题标题】:Controlling race condition at startup在启动时控制竞争条件
【发布时间】:2010-05-21 07:02:45
【问题描述】:

我有一些代码想要执行一次初始化。但是这段代码没有明确的生命周期,所以在我的初始化完成之前,我的逻辑可能会被多个线程调用。所以,我想基本上确保我的逻辑代码“等待”直到初始化完成。

这是我的第一次剪辑。

public class MyClass {
    private static final AtomicBoolean initialised = new AtomicBoolean(false);

    public void initialise() {
        synchronized(initialised) {
            initStuff();
            initialised.getAndSet(true);
            initialised.notifyAll();
        }
    }

    public void doStuff() {
        synchronized(initialised) {
            if (!initialised.get()) {
                try {
                    initialised.wait();
                } catch (InterruptedException ex) {
                    throw new RuntimeException("Uh oh!", ex);
                }
            }
        }

        doOtherStuff();
    }
}

我基本上想确保这会做我认为它会做的事情——阻止 doStuff 直到初始化为真,并且我不会错过 doStuff 可能卡在对象上的竞争条件。 wait() 永远不会到达。

编辑:

我无法控制线程。而且我希望能够控制所有初始化完成的时间,这就是 doStuff() 不能调用 initialise() 的原因。

我使用了 AtomicBoolean,因为它是值持有者和我可以同步的对象的组合。我也可以简单地拥有一个“public static final Object lock = new Object();”和一个简单的布尔标志。 AtomicBoolean 方便地给了我两个。不能修改布尔值。

CountDownLatch 正是我想要的。我还考虑过使用具有 0 个许可证的 Sempahore。但是 CountDownLatch 非常适合这项任务。

【问题讨论】:

标签: java concurrency


【解决方案1】:

这是库和内置并发控制的奇怪组合。这样的东西更干净:

public class MyClass {

  private static final CountDownLatch latch = new CountDownLatch(1);

  public void initialise() {
    initStuff();
    latch.countDown();
  }

  public void doStuff() {
    try {
      latch.await();
    } catch (InterruptedException ex) {
      throw new RuntimeException("Uh oh!", ex);
    }
    doOtherStuff();
  }

}

【讨论】:

  • CountDownLatch 达到零时是否需要同步/锁定?
  • +1 - CountDownLatch.await 在达到零时不会阻塞 - 只需检查 javadocs。
  • 没错,我刚刚检查了源代码。计数为零后,await() 调用 Thread.interrupted() 并执行一次 volatile 读取。
【解决方案2】:

synchronized 块会自动阻塞其他线程。只需使用一个简单的锁对象 + 状态变量:

public class MyClass {
    private static boolean initialised;
    private static final Object lockObject = new Object();

    public void initialise() {
        synchronized (lockObject) {
            if (!initialised) {
                initStuff();
                initialised = true;
            }
        }
    }

    public void doStuff() {
        initialise();
        doOtherStuff();
    }
}

【讨论】:

  • 如果您总是从同步块内部访问布尔值,则该布尔值不需要是可变的。
  • 哎呀,谢谢。我从双重检查锁定 + volatile 开始,然后认为对于这个简单的场景不值得混淆。即使接受了另一个答案,也已修复。
【解决方案3】:

最好的方法是使用静态初始化器(如 SB 所述):

public class MyClass {

    public static void doInitialize() {
      ...
    }

    public void doStuff() {
        doOtherStuff();
    }

    static {
       doInitialize();
    }
}

这将在允许调用任何其他代码之前执行一次。如果您总是必须在使用该类的任何时候进行初始化,那么不会影响性能,因为在使用该类之前不会加载该类。有关详细信息,请参阅this question 的答案。

【讨论】:

    【解决方案4】:

    既然是在启动的时候,为什么不等到初始化完成再启动其他线程呢?

    此外,您可以执行一个线程同步的 IsComplete 布尔值,将其设置为 false,直到它被初始化例程设置为 true。

    【讨论】:

      【解决方案5】:

      您总是在同步块内使用AtomicBoolean。没有太多意义,因为只有一个线程可以访问它。原子变量旨在用于无锁解决方案 - 您可以将值设置为不可中断的单元。

      我猜你正在寻找初始化发生后的无锁解决方案:

      public class MyClass {
          private static final AtomicBoolean initialised = new AtomicBoolean(false);
      
          public void initialise() {
              if (!intialized.get())
              {
                  synchornized (this)
                  {
                     if (!initialized.getAndSet(true))
                        doInitialize();
                  }
              }
          }
      
          public void doStuff() {
              initialize();
              doOtherStuff();
          }
      

      您也可以使用简单的volatile boolean 来执行此操作,这实际上比 AtomicBoolean 更有效。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-29
        • 1970-01-01
        • 2022-01-23
        • 2018-10-08
        相关资源
        最近更新 更多