4

volatileJavaでの変数の使用について読んでいます。システム内のさまざまなコア/プロセッサで実行されているすべてのスレッドに対する最新の更新を即座に確認できることを理解しています。ただし、これらの更新の原因となった操作の原子性は保証されません。以下の文献がよく使われているのを見かけます

揮発性フィールドへの書き込みは、同じフィールドのすべての読み取りの前に発生します。

これは私が少し混乱しているところです。これは、クエリをよりよく説明するのに役立つコードのスニペットです。

volatile int x = 0;
volatile int y = 0; 

Thread-0:                       |               Thread-1:
                                |
if (x==1) {                     |               if (y==1) {
     return false;              |                    return false; 
} else {                        |               } else {
     y=1;                       |                   x=1;
     return true;               |                   return true;
}                               |               }

x & y は bothvolatileであるため、次の先行発生エッジがあります。

  1. スレッド 0 での y の書き込みとスレッド 1 での y の読み取りの間
  2. スレッド 1 での x の書き込みとスレッド 0 での x の読み取りの間

これは、どの時点でも、「else」ブロックに入れることができるスレッドは1つだけであることを意味しますか(読み取りの前に書き込みが発生するため)?

Thread-0 が起動し、x をロードし、その値が 0 であることを検出し、else ブロックに y を書き込む直前に、y をロードする Thread-1 へのコンテキスト スイッチがあり、値が 0 であることを検出する可能性があります。したがって、else ブロックにも入ります。そのようなコンテキストの切り替えを防ぎますかvolatile(非常にありそうにないようです)?

4

7 に答える 7

5

したがって、この質問は雑草の中にあると思います。要点はvolatile、変数の値が現在のスレッドの範囲外で変更される可能性があり、その値は使用前に常に読み取られる必要があることを示しています。

原則として、引用しているステートメントは、値を現在のスレッドに置き換える前に、値が読み取られることを実際に言っています。

あなたの例は競合状態であり、両方のスレッドが true を返すか、どちらも true を返さないか、それぞれが異なる値を返す可能性があります-のセマンティクスはvolatile、例の実行を定義しません (コンパイルして実行することをお勧めします出力が変化することを確認します)。

の動作を説明する一般的な方法volatileは、2 つのスレッドを実行することです。1 つのスレッドが共有状態を更新し、フィールドがマークされている場合とマークされていない場合に何が起こるかを確認します。

class VolatileTest implements Runnable
{
        // try with and without volatile
        private volatile boolean stopRunning = false;

        public void triggerStop(){
             stopRunning = true;
        }

        @Override
        public void run(){
             while(!stopRunning);
             System.out.println("Finished.");
        }

        public static void main (String[] args) throws java.lang.Exception
        {
            final VolatileTest test = new VolatileTest();
            new Thread(test).start();
            Thread.sleep(1000);
            test.triggerStop() = false;
        }
}

この例では、各反復で値を読み取る必要がないため、がマークされていない限り、 as のマークを付けないと、ループが永遠に続くstopRunning可能volatile性があります。whilestopRunningvolatile

于 2012-09-22T03:04:13.803 に答える
2

揮発性のセマンティクス

あなたが言及している問題は、Dekker's Algorithm のバリエーションです。さまざまな実装とそれに関する詳細について、Googleで多くの詳細が示されています。

2 つのプロセスが同時にクリティカル セクションに入ろうとした場合、アルゴリズムは、順番に基づいて 1 つのプロセスのみを許可します。1 つのプロセスがすでにクリティカル セクションにある場合、他のプロセスはビジー状態になり、最初のプロセスが終了するまで待機します。これは、フラグ [0] とフラグ [1] の 2 つのフラグを使用して行われます。フラグ [0] とフラグ [1] は、クリティカル セクションに入る意図と、2 つのプロセス間で誰が優先されるかを示すターン変数を示します。

ウィキペディアは、volatileデッカーのアルゴリズムとの関連性をカバーしています

揮発性情報

しかし、この記事volatileは1 つの文で完全に説明されていることがわかりました。

変数が volatile として宣言されている場合、フィールドを読み取るすべてのスレッドが、最後に書き込まれた値を見ることが保証されます。(ラース・フォーゲル、2008)

基本的に、volatile は、変数の値が異なるスレッドによって変更されることを示すために使用されます。(ジャバメックス、2012)

Massey University : Concurrency Volatile 講義スライド

マッセイ講義スライド
(出典: iforce.co.nz )

出典: Hans W. Guesgen 教授

まだ理解していない場合はvolatile、仕組みを調べてくださいatomicity

于 2012-09-22T03:22:30.320 に答える
1

これは、どの時点でも、スレッドの1つだけがその「else」ブロックに入ることができることを意味しますか(書き込みは読み取りの前に発生するため)?

いいえ、違います。

揮発性はそのようなコンテキストスイッチを防ぎますか(非常にありそうもないようです)?

いいえ、それを防ぐことはできません。あなたが特定した競合状態は本当にそこにあります。

これは、揮発性物質が汎用の同期メカニズムではないという事実を示しています。むしろ、単一のメモリ操作で読み取りまたは書き込みが可能な単一の変数を中心に展開する特定の状況での同期を回避できます。

あなたの例では、2つの変数があります...

于 2012-09-22T03:35:59.253 に答える
0

揮発性は可視性を保証し、同期は可視性と原子性の両方を保証します

それが提供することを保証するのは、1つのスレッドによって更新された場合、xまたはy更新がすべてのスレッドに表示される場合のみです。実行の順序は、スレッドの実行方法にのみ依存します。入力する前に比較が行われるため、コンテキストスイッチの質問に対する答えはノーです。これは1回だけであり、その時点での変数の値に基づいています。これは、同じ変数に対して複数の比較がある場合に可能になる可能性があります。

volatileを使用する最良の方法は、クラスの状態変数を維持することです。新しい割り当てのどこに、そのクラスにアクセスしているすべてのスレッドが反映されます

ThreadPoolExecutorいくつかの状態変数のソースコードを見ると、 揮発性です。

それらの1つは、runStateの状態を決定するものThreadPoolExecutorです。shutdown()これで、が呼び出されたときにrunStateが更新されSHUTDOWN 、タスクを受け入れるメソッドがexecute submit、たまたま単純なタスクの受け入れを停止するようになります。if(runState==RUNNING)

以下のコードはThreadPoolExecutorexecuteのメソッドです

以下のコードでは、変数は揮発性の状態変数であるためpoolSize、追加の同期が必要ない場合。runState

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    if (**poolSize** >= corePoolSize || !addIfUnderCorePoolSize(command)) {//poolSize  volatile
        if (**runState** == RUNNING && workQueue.offer(command)) {//runstate volatile
            if (**runState** != RUNNING || **poolSize** == 0)//runstate volatile
                ensureQueuedTaskHandled(command);
        }
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated
    }
  }

使わないときは?

変数をアトミック操作で使用する必要がある場合、または1つの変数値が別の変数値に依存している場合(Increment i ++など)。

アトミック変数はより優れた揮発性と呼ばれ、メモリのセマンティクスは揮発性ですが、追加の操作を提供します。たとえば、AtomicIntegerによって提供されるincrementAndGet()操作。

于 2012-09-22T03:37:17.937 に答える
0

すべての変数に対するすべての揮発性の読み取りと書き込みは、完全な順序 (同期順序) です。

揮発性変数 v への書き込みは、任意のスレッドによる v の後続のすべての読み取りの前に発生します (「後続」は同期順序に従って定義されます)。

http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.4

于 2012-09-22T03:11:24.163 に答える
0

ここに悪い例があると思います。どちらのスレッドも簡単に else ブロックに入ることができます (スレッド 0 は x を読み取って 1 に対してチェックし、次にスレッド 1 は y を読み取って 1 に対してチェックします)。

興味深い例は次のようなものです。

thread 0          thread 1

x = 1             y = 1
if (y == 0) {     if (x == 0) {
}                 }

とが揮発性である場合、両方のスレッドがifブロックに入ることができません。xy

于 2012-09-22T03:04:21.167 に答える
0

Java 言語仕様によると、volatile フィールドには次のプロパティがあります。

フィールドは volatile と宣言される場合があります。この場合、スレッドは、変数にアクセスするたびに、フィールドの作業コピーとマスター コピーを調整する必要があります。さらに、スレッドに代わって 1 つまたは複数の volatile 変数のマスター コピーに対する操作が、スレッドが要求した順序どおりにメイン メモリによって実行されます。

以上より、

これは、どの時点でも、「else」ブロックに入れることができるスレッドは1つだけであることを意味しますか(読み取りの前に書き込みが発生するため)?

volatile は、スレッドがローカル コピーからではなく、メモリ内の変数の最新の値を読み取ることを保証するため、これは発生しません。変数を揮発性として宣言することによってブロックされる可能性はありません。synchronizedこの概念を使用して、変数への書き込みをシリアル化する必要があります。

于 2012-09-22T03:05:03.880 に答える