首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用AtomicBoolean控制实例的状态

用AtomicBoolean控制实例的状态
EN

Stack Overflow用户
提问于 2016-03-18 13:31:31
回答 2查看 451关注 0票数 0

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

代码语言:javascript
复制
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);
    }
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-03-18 13:44:31

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

首先,在active,closed "tuple“的4个可能值中,只有3个是有意义的,将两者都设置为true会导致无效状态(可能是良性的,但仍然是无效的)。你可能会认为它是纯粹的学究,但一个清晰的设计往往会带来其他好处。

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

代码语言:javascript
复制
 active.set(false);
 // <-- what if someone calls start() here?
 closed.set(true); //I assume you wanted to set it to true

正如您从我的评论中可以看到的那样,在您将start()设置为false之后,但在将closed设置为true之前,可能会有人调用closed

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

这里还有另一个单独的问题:您遵循的模式是调用get()来检查值,然后稍后将其转换为其他值。作为PetrosP pointed it out,这不是一个原子操作,您可以调用start() 1000次,它们都将active看作false。您需要使用compareAndSet,而原子(这是Atomic*类的全部要点),从而保证只有一个线程可以提升状态标志。

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

代码语言:javascript
复制
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.
    }
}
票数 5
EN

Stack Overflow用户

发布于 2016-03-18 13:43:24

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

解决办法可以是:

代码语言:javascript
复制
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);
        }
    }
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/36085736

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档