29

JavaAtomicReferenceFieldUpdaterドキュメントから:

このクラスのメソッドの保証は、compareAndSet他のアトミッククラスよりも弱いことに注意してください。このクラスは、フィールドのすべての使用がアトミックアクセスの目的に適切であることを保証できないため、およびの他の呼び出しに関してのみ、アトミック性と揮発性セマンティクスを保証できcompareAndSetますset

つまり、通常の揮発性書き込みをと一緒に実行することはできませんが、代わりcompareAndSetに使用する必要があります。setについては何も言及していませんget

それは、同じ原子性の保証で揮発性フィールドをまだ読み取ることができることを意味します-setまたはの前にすべての書き込みcompareAndSetがあり、揮発性フィールドを読み取ったすべての人に表示されますか?

または、フィールドでの揮発性読み取りgetの代わりにを使用する必要がありますか?AtomicReferenceFieldUpdater

参考文献をお持ちの場合は投稿してください。

ありがとうございました。

編集:

Java Concurrency in Practiceから、彼らが言うのは次のことだけです。

アップデータクラスのアトミック性の保証は、基になるフィールドが直接変更されないことを保証できないため、通常のアトミッククラスよりも弱くなります。compareAndSetメソッドと算術メソッドは、アトミックフィールドアップデータメソッドを使用する他のスレッドに関してのみアトミック性を保証します。

繰り返しますが、他のスレッドがこれらの揮発性フィールドを読み取る方法については言及されていません。

また、「直接変更」は通常の揮発性書き込みであると想定するのは正しいですか?

4

3 に答える 3

25

アトミックのパッケージドキュメントで説明されているように(一般的に、アップデーターではありません):

アトミックのアクセスと更新のメモリー効果は、一般的に揮発性物質の規則に従います[...]:

  • getvolatile変数を読み取るメモリ効果があります。
  • set変数を書き込む(割り当てる)というメモリ効果がありvolatileます。
  • [...]
  • compareAndSetまた、変数getAndIncrementの読み取りと書き込みの両方のメモリ効果があるなど、他のすべての読み取りおよび更新操作。volatile

アトミックcompareAndSetが解決しようとしている問題は何ですか?なぜ(たとえば)atomicInteger.compareAndSet(1,2)の代わりに使用するのif(volatileInt == 1) { volatileInt = 2; }ですか?同時読み取りの問題は、通常のによってすでに処理されているため、解決しようとはしていませんvolatile。(「揮発性」の読み取りまたは書き込みは、「アトミック」読み取りまたは書き込みと同じです。同時読み取りは、書き込みの途中で発生した場合、またはステートメントが問題のある方法で並べ替えまたは最適化された場合にのみ問題になります。しかし、volatileすでにそれらのことを防いでいます。)compareAndSet解決する唯一の問題は、アプローチでは、読み取り時()と書き込み時()の間にvolatileInt、他のスレッドが同時書き込みを行う可能性があることです。volatileIntvolatileInt == 1volatileInt = 2compareAndSetその間に競合する書き込みをロックアウトすることにより、この問題を解決します。

これは、「アップデーター」(AtomicReferenceFieldUpdaterなど)の特定の場合にも同様に当てはまります。volatile 読み取りはまだ桃色です。アップデータのcompareAndSetメソッドの唯一の制限は、上記で書いたように「競合する書き込みをロックアウトする」のではなく、 ;の同じインスタンスからの競合する書き込みのみをロックアウトすることです。AtomicReferenceFieldUpdatervolatileフィールドを直接同時に更新している場合(または、複数AtomicReferenceFieldUpdaterのを同時に使用して同じvolatileフィールドを更新している場合)は、保護できません。(ちなみに、あなたの見方によっては、同じことが当てはまりAtomicReferenceます。自分のセッターをバイパスする方法でフィールドを更新した場合、彼らはあなたを保護できませんでした。違いは、AtomicReference実際にはそのフィールドを所有しており、そのprivateため、外部の手段でフィールドを変更することに対して警告する必要はありません。)

volatileしたがって、あなたの質問に答えるために:はい、部分的/一貫性のない読み取り、並べ替えられたステートメントなどに対して、同じアトミック性が保証されたフィールドを引き続き読み取ることができます。


追加のために編集(12月6日):この主題に特に興味がある人は、おそらくすぐ下の議論に興味があるでしょう。その議論からの重要なポイントを明確にするために、答えを更新するように求められました。

  • 追加する最も重要な点は、上記がドキュメントの私自身の解釈であるということだと思います。私はそれを正しく理解しており、他の解釈は意味をなさないとかなり確信しています。そして、必要に応じて、長さで論点を議論することができます;-); しかし、私も他の誰も、質問自体で言及された2つのドキュメント(クラスのJavadocとJava Concurrency in Practice)と私の元の回答で言及された1つのドキュメントよりも、この点に明示的に対処する信頼できるドキュメントへの参照を作成していません。上記(パッケージのJavadoc)。

  • 次の最も重要な点は、のドキュメントにAtomicReferenceUpdaterは揮発性の書き込みと混合するのは安全ではないと書かれていますが、一般的なプラットフォームでは実際に安全compareAndSetであると信じているということです。一般的な場合にのみ安全ではありません。パッケージドキュメントからの次のコメントのためにこれを言います:

    これらのメソッドの仕様により、実装では、最新のプロセッサで利用できる効率的なマシンレベルのアトミック命令を使用できます。ただし、一部のプラットフォームでは、サポートに何らかの形式の内部ロックが必要になる場合があります。したがって、メソッドが非ブロッキングであることが厳密に保証されているわけではありません。スレッドは、操作を実行する前に一時的にブロックする可能性があります。

    それで:

    • 最新のプロセッサの一般的なJDK実装では、揮発性書き込みに関してアトミックなコンペアアンドスワップ操作を使用するため、AtomicReference.set単純に揮発性書き込みを使用します。別のオブジェクトのフィールドを更新するためにリフレクションのようなロジックを使用する必要があるため、は必然的により複雑になりますが、それがより複雑な唯一の理由であると私は主張します。典型的な実装は、を呼び出します。これは、長い名前による揮発性の書き込みです。AtomicReference.compareAndSetAtomicReferenceUpdater.setAtomicReference.setUnsafe.putObjectVolatile
    • ただし、すべてのプラットフォームがこのアプローチをサポートしているわけではなく、サポートしていない場合は、ブロックが許可されます。compareAndSet過度に単純化するリスクがありますが、これは、アトミッククラスが(多かれ少なかれ)synchronizedを使用するメソッドに直接適用するgetことsetで実装できることを大まかに意味すると解釈します。しかし、これが機能するためには、上記の私の元の回答で説明されている理由から、setもそうである必要があります。synchronizedつまり、呼び出し後、呼び出しcompareAndSetgetにフィールドを変更する可能性があるため、単に揮発性の書き込みにすることはできません。compareAndSetset
    • 言うまでもなく、私の最初の答えである「ロックアウト」というフレーズの使用は、文字通りに解釈されるべきではありません。通常のプラットフォームでは、非常にロックのような必要は発生しないからです。
  • SunのJDK1.6.0_05の実装ではjava.util.concurrent.ConcurrentLinkedQueue<E>、次のことがわかります。

    private static class Node<E> {
        private volatile E item;
        private volatile Node<E> next;
        private static final AtomicReferenceFieldUpdater<Node, Node> nextUpdater =
            AtomicReferenceFieldUpdater.newUpdater(Node.class, Node.class, "next");
        private static final AtomicReferenceFieldUpdater<Node, Object> itemUpdater =
            AtomicReferenceFieldUpdater.newUpdater(Node.class, Object.class, "item");
        Node(E x) { item = x; }
        Node(E x, Node<E> n) { item = x; next = n; }
        E getItem() { return item; }
        boolean casItem(E cmp, E val)
            { return itemUpdater.compareAndSet(this, cmp, val); }
        void setItem(E val) { itemUpdater.set(this, val); }
        Node<E> getNext() { return next; }
        boolean casNext(Node<E> cmp, Node<E> val)
            { return nextUpdater.compareAndSet(this, cmp, val); }
        void setNext(Node<E> val) { nextUpdater.set(this, val); }
    }
    

    (注:コンパクトに調整された空白)。インスタンスが構築されると、揮発性の書き込みはありません。つまり、すべての書き込みはAtomicReferenceFieldUpdater.compareAndSetまたはを介しAtomicReferenceFieldUpdater.setて行われますが、揮発性の読み取りは、を1回呼び出すことなく自由に使用できるように見えますAtomicReferenceFieldUpdater.get。JDK 1.6のその後のリリースは、Unsafe直接使用するように変更されました(これは、OracleのJDK 1.6.0_27によって発生しました)が、JSR 166メーリングリストでの議論は、この変更を、以前の実装の正確さに関する問題ではなく、パフォーマンスの考慮事項に起因するとしています。

    • しかし、これは防弾の権威ではないことを指摘しなければなりません。便宜上、「Sunの実装」を単一のものであるかのように記述しますが、以前の箇条書きでは、プラットフォームごとにJDKの実装で異なる処理が必要になる可能性があることを明確にしています。上記のコードは、プラットフォームに依存しない方法で記述されているように見えます。これは、 AtomicReferenceFieldUpdater.set;への呼び出しを優先して単純な揮発性の書き込みを避けているためです。しかし、一方のポイントの私の解釈を受け入れない人は、もう一方のポイントの私の解釈を受け入れない可能性があり、上記のコードはすべてのプラットフォームで安全であるとは限らないと主張する可能性があります。
    • この権限のもう1つの弱点は、へのNode呼び出しと同時に揮発性の読み取りを実行できるように見えますがAtomicReferenceFieldUpdater.compareAndSet、それはプライベートクラスであるということです。そして私は、その所有者()が実際にそのような電話をかけているという証拠を、ConcurrentLinkedQueueそれ自身の予防策なしに引き受けていません。(しかし、私はその主張を証明していませんが、誰もがそれについて異議を唱えることはないと思います。)

この補遺の背景と詳細については、以下のコメントを参照してください。

于 2011-11-29T23:12:32.583 に答える
2

つまり、オブジェクトへの参照は保証されますが、任意のオブジェクトを使用できるため、別のスレッドがオブジェクトにアクセスするときに、そのオブジェクトのフィールドが正しく書き込まれない可能性があります。

保証できる唯一の方法は、フィールドが最終的または揮発性であるかどうかです。

于 2011-11-25T00:25:00.180 に答える
2

これは質問の正確な答えではありません:

説明も意図も、ドキュメントからは明確に見えません。グローバルな順序付け(IBM PowerやARMなど)を可能にするアーキテクチャーでの揮発性書き込みをバイパスし、フェンシングなしでCAS(LoadLinked / StoreCondition)の動作を公開するというアイデアの場合、それは非常に驚くべき努力と混乱の原因になります。

sun.misc.UnsafeのCASには、仕様または順序の保証(以前に発生したものとして知られています)はありませんが、java.util.atomic...にはあります。したがって、弱いモデルではjava.util.atomicimpl。この場合、Java仕様に従うために必要なフェンスが必要になります。

アップデータクラスが実際にフェンスを欠いていると仮定します。その場合、フィールドの揮発性読み取り(getを使用しない)は更新値を返します。つまり、明らかget()に不要です。注文の保証がないため、以前のストアは伝播されない可能性があります(弱いモデルの場合)。x86 / Sparcでは、TSOハードウェアがJava仕様を保証します。

ただし、これは、CASを次の不揮発性読み取りで並べ替えることができることも意味します。キューから興味深いメモがあります:java.util.concurrent.SynchronousQueue

        // Note: item and mode fields don't need to be volatile
        // since they are always written before, and read after,
        // other volatile/atomic operations.

言及されているすべてのアトミック操作は、まさにAtomicReferenceFieldUpdaterのCASです。これは、通常の読み取りAND書き込みとAtomicReferenceFieldUpdater.CASの間の欠如または調整、つまり揮発性書き込みのように機能することを意味します。

        s.item = null;   // forget item
        s.waiter = null; // forget thread

        //....

        while ((p = head) != null && p != past && p.isCancelled())
            casHead(p, p.next);

CASのみで、揮発性の書き込みはありません。

上記の条件を考えると、AtomicXXXFieldUpdaterは対応するAtomicXXXと同じセマンティクスを公開していると結論付けます。

于 2011-11-30T02:06:22.800 に答える