【问题标题】:Controlling an instance's state with AtomicBoolean使用 AtomicBoolean 控制实例的状态
【发布时间】:2016-03-18 13:31:31
【问题描述】:

我需要确保特定的启动和停止代码在每个实例生命周期中只执行一次,并且不能“重新启动”实例。对于多个线程可能作用于实例的场景,以下代码是否足够?

public final class MyRunnable {
    private final AtomicBoolean active = new AtomicBoolean(false);
    private final AtomicBoolean closed = new AtomicBoolean(false);

    public void start() {
      if (closed.get()) {
        throw new IllegalStateException("Already closed!");
      }
      if (active.get()) {
        throw new IllegalStateException("Already running!");
      }

      active.set(true);

      // My one-time start code.

      // My runnable code.
    }

    public void stop() {
      if (closed.get()) {
        throw new IllegalStateException("Already stopped!");
      }
      if (!active.get()) {
        throw new IllegalStateException("Stopping or already stopped!");
      }

      active.set(false);

      // My one-time stop code.

      closed.set(true);
    }
}

【问题讨论】:

  • closed.set(false); 我想你的意思是closed.set(true);

标签: java concurrency lifecycle atomic-values


【解决方案1】:

出于两个原因,我会选择单一的 3 值状态。

首先,在active,closed“元组”的 4 个可能值中,只有 3 个有意义,将两者都设置为 true 会导致(可能是良性的,但仍然是)无效状态。您可能会将其视为纯粹的迂腐,但清晰的设计通常会带来其他好处。

这巧妙地将我们引向了第二个更可怕的原因:

 active.set(false);
 // <-- what if someone calls start() here?
 closed.set(true); //I assume you wanted to set it to true

正如您从我的评论中看到的那样,您在那里有一个脆弱点,可以想象有人可以在您将 active 设置为 false 之后但在您将 closed 设置为 true 之前调用 start() .

现在你可能只是说“好吧,让我们交换这两个然后先设置closed”,但是你必须解释为什么这两个肯定不会被 JVM 重新排序。您最终可能会将两个标志都设置为 true,从而导致上述“无效状态”。

这里还有另一个单独的问题:您遵循的模式是调用get() 来检查该值,然后再调用set() 来处理其他事情。作为PetrosP pointed it out,这不是原子操作,您可以调用start() 1000 次,所有这些都将active 视为false。您需要改用compareAndSet,它原子的(这是Atomic* 类的全部要点),从而保证只有一个线程可以推进状态标志。

所以让我们将两者结合起来,使用单个 3 值状态(为简单起见,我使用了 AtomicInteger,但您可以使用 AtomicReference 和真正的 enum)和 compareAndSet()

public final class MyRunnable {
    private static final int READY_TO_START = 0;
    private static final int ACTIVE = 1;
    private static final int STOPPED = 2;
    private final AtomicInteger status = new AtomicInteger(READY_TO_START);

    public void start() {
      if (!status.compareAndSet(READY_TO_START, ACTIVE)) {
        throw new IllegalStateException("Already started");
      }

      // My one-time start code.
    }

    public void stop() {
        if (!status.compareAndSet(ACTIVE, STOPPED)) {
            throw new IllegalStateException("Can't stop, either not started or already stopped");
        }

      // My one-time stop code.
    }
}

【讨论】:

  • 一个小问题 - 保证(在正确的编译器中)两个 AtomicBoolean.set() 调用不会被重新排序,因为它们都转换为 volatile 存储。这些之间应该有一个StoreStore 屏障:g.oswego.edu/dl/jmm/cookbook.html
  • @DimitarDimitrov 是的,这是有保证的,但我的意思是,仅仅通过查看代码并不是很明显。我已经澄清了这一点。
【解决方案2】:

这个解决方案是不够的。考虑这种情况:两个线程同时进入 start()。一个调用active.get() 并返回false。然后第二个调用active.get(),它也得到false。在这种情况下,它们都将继续。然后第一个将 active 设置为 true。此时第二个也将 active 设置为 true,它们都将继续执行应该运行一次的其余代码。

解决方案可能是这样的:

public final class MyRunnable {
    private final AtomicBoolean active = new AtomicBoolean(false);
    private final AtomicBoolean closed = new AtomicBoolean(false);

    public void start() {
        synchronized (this) {
            if (closed.get()) {
                throw new IllegalStateException("Already closed!");
            }
            if (active.get()) {
                throw new IllegalStateException("Already running!");
            }

            active.set(true);
        }

        // My one-time start code.

        // My runnable code.
    }

    public void stop() {
        synchronized (this) {
            if (closed.get()) {
                throw new IllegalStateException("Already stopped!");
            }
            if (!active.get()) {
                throw new IllegalStateException("Stopping or already stopped!");
            }

            // My one-time stop code.

            closed.set(false);
            active.set(false);
        }
    }
}

【讨论】:

  • 这解决了问题,但是如果你把synchronized 放在所有东西上,你就会失去使用Atomic* 类的好处。
  • 是的,你是对的。我正要写评论说你的解决方案到目前为止更好。不幸的是我不能投票,因为我没有足够的声誉:-)
猜你喜欢
  • 1970-01-01
  • 2013-04-15
  • 1970-01-01
  • 2018-05-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多