使用AtomicBoolean控制实例的状态

时间:2016-03-18 13:31:31

标签: java concurrency lifecycle atomic-values

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

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

2 个答案:

答案 0 :(得分:4)

出于两个原因,我会选择一个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

从我的评论中可以看出,你在那里有一个易受伤害的地方,在你设置start()active之后但在你设置{之前,有人可以想象召唤false {1}}至closed

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

此处还有另一个单独的问题:您遵循的模式是调用true来检查值,然后get()将其更改为其他内容。如PetrosP pointed it out,这不是原子操作,您可以调用set() 1000次,所有人都将start()视为active。您需要使用false,而原子(这是compareAndSet类的整点),从而保证只有一个线程可以提升状态标志

因此,让我们将两者结合起来,使用一个3值状态(为了简单起见,我使用了Atomic*,但您可以使用AtomicInteger和真AtomicReference)和{{ 1}}:

enum

答案 1 :(得分:1)

此解决方案还不够。考虑这种情况:两个线程同时进入start()。一个人调用active.get(),然后返回false。然后第二个调用active.get(),它也会获得false。在这种情况下,他们将继续。然后第一个将激活设置为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);
        }
    }
}