0

特定の開始コードと停止コードがインスタンスのライフサイクルごとに 1 回だけ実行され、インスタンスが「再起動」されないようにする必要があります。次のコードは、複数のスレッドがインスタンスで動作している可能性があるシナリオに適していますか?

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

2 に答える 2

5

2 つの理由から、単一の 3 値ステータスを使用します。

まず、active,closed「タプル」の 4 つの可能な値のうち、意味のある値は 3 つだけです。両方を設定するtrueと、(おそらく良性ではありますが、それでも) 無効な状態になります。純粋な衒学者として片付けてしまうかもしれませんが、明確なデザインは多くの場合、他の利点をもたらします。

これは、2 番目の恐ろしい理由につながります。

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

私のコメントからわかるように、そこには脆弱な場所がありstart()ます. に設定activeした後、 に設定するfalse前に誰かが電話をかける可能性があります.closedtrue

ここで、「よし、2 つを交換して最初に設定しよう」と言うだけでよいかもしれませclosedんが、JVM によって 2 つが確実に並べ替えられない理由を説明する必要があります。そして、両方のフラグが に設定される可能性がありtrue、上記の「無効な状態」になります。

ここには別の問題があります。従うパターンはget()、値をチェックするために呼び出してから、set()後で別のものを呼び出すことです。PetrosPが指摘したようstart()に、これはアトミック操作ではありません。代わりに使用する必要があります。これアトミックです (これがクラスの要点です)。したがって、1 つのスレッドのみがステータス フラグを進めることができることが保証されます。activefalsecompareAndSetAtomic*

それでは、単一の 3 つの値を持つステータス ( AtomicInteger簡単にするために使用しましたがAtomicReference、 trueを使用できますenum) とを使用して、2 つを組み合わせましょう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.
    }
}
于 2016-03-18T13:44:31.453 に答える
1

この解決策は十分ではありません。このシナリオを考えてみましょう: 2 つのスレッドが同時に start() に入ります。1 つが呼び出さactive.get()れ、それがfalse返されます。次に、2番目の呼び出しが行われactive.get()、それも取得falseされます。この場合、両者は継続します。次に、最初のものはアクティブをtrueに設定します。その時点で 2 番目のものもアクティブを true に設定し、両方とも 1 回実行する必要がある残りのコードに進みます。

解決策は次のとおりです。

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);
        }
    }
}
于 2016-03-18T13:43:24.057 に答える