7

アトミックに別の値と交換できるタイプの参照を実装する方法はありますか?


Java では、AtomicReferenceこれをローカル変数と交換できますが、別の変数と交換することはできませんAtomicReference

できるよ:

AtomicReference r1 = new AtomicReference("hello");
AtomicReference r2 = new AtomicReference("world");

2 つの操作の組み合わせでそれらを交換します。

r1.set(r2.getAndSet(r1.get()));

しかし、これにより、両方に が含まれているという矛盾した状態になります"hello"。また、それらをアトミックに交換できたとしても、(ペアとして) アトミックに読み取ることはできませんでした。


私ができるようにしたいのは:

PairableAtomicReference r1 = new PairableAtomicReference("hello");
PairableAtomicReference r2 = new PairableAtomicReference("world");
AtomicRefPair rp = new AtomicRefPair(r1, r2);

それから

Object[] oldVal, newVal;
do {
    oldVal = rp.get();
    newVal = new Object[] {oldVal[1], oldVal[0]};
} while (! rp.compareAndSet(oldVal, newVal));

値を交換し、別のスレッドで:

AtomicRefPair otherRP = new AtomicRefPair(r1, r2);
System.out.println(Arrays.toString(otherRP.get()));

[hello, world]出力が または のいずれかになることを確認して[world, hello]ください。

ノート:

  • r1この操作ではとr2がペアになっていますが、別のスレッドが独立してペアになる可能性があります (たとえばr1、別のスレッドと) (残念ながら、このソリューションr3を使用できないことを意味します)。
  • これらの参照は数十万になるため、グローバルReentrantLockが大きなボトルネックになります。
  • rp必ずしもスレッド間で共有されるとotherRPは限らないため、単純にロックするだけでは機能しません。それらはインターンされる可能性がありますが、インターンプールには独自の同期が必要であり、これが別のボトルネックになります。
  • ここでは 2 つの参照のグループしか作成していませんが、3 つ以上をグループ化する機能はおまけです。

のロックフリーバージョンを実装することは可能AtomicRefPairですか? そうではないという予感がありますが、そうでない場合は、その理由を説明する記事がどこかにあるのではないでしょうか?


関連: C# で 2 つの int をアトミックに交換するにはどうすればよいですか?

4

2 に答える 2

4

ペアを保持する不変のクラスを持ちます。それがあなたの原子です。ペアを交換するとは、原子を交換することを意味します。

更新:あなたの質問はあまり明確ではありません。しかし、一般に、複数の変数からなる並行システムでは、

  1. システム状態のスナップショットを取得します。スナップショットは、一度取得すると変更されません。
  2. 複数の変数を一度に変更することで、システムの状態をアトミックに更新します。私の更新と以前のスナップショットの間に他の更新がないことが必要になる場合があります(私の計算はそれに基づいていました)

リソースをあまり消費しない場合は、システムをスナップショットで直接モデル化できます。

于 2011-01-26T00:35:37.917 に答える
3

良い解決策があるかどうかはわかりませんが、次の醜い解決策が機能する可能性があります。

public final class MyReference<T> extends ReentrantLock implements Comparable<MyReference<T>> {
    public MyReference() {
        id = counter.incrementAndGet();
    }

    public void swap(MyReference<T> other) {
        if (id < other.id) {
            lock();
            other.lock();
        } else {
            other.lock();
            lock();
        }
        final T tmp = value;
        value = other.value;
        other.value = tmp;
        unlock();
        other.unlock();
    }

    public static <T> List<T> consistentGet(List<MyReference<T>> references) {
        final ArrayList<MyReference<T>> sortedReferences = Lists.newArrayList(references);
        Collections.sort(sortedReferences);
        for (val r : sortedReferences) r.lock();
        final List<T> result = Lists.newArrayListWithExpectedSize(sortedReferences.size());
        for (val r : references) result.add(r.value);
        for (val r : sortedReferences) r.unlock();
        return result;
    }

    @Override
    public int compareTo(MyReference<T> o) {
        return id < o.id ? -1 : id > o.id ? 1 : 0;
    }

    private final static AtomicInteger counter = new AtomicInteger();

    private T value;
    private final int id;
}
  • AtomicReference の代わりに MyReference を使用します。
  • 多くのロックを使用しますが、どれもグローバルではありません。
  • ロックを一定の順序で取得するため、デッドロックが発生しません。
  • これは、lombok と guava を使用してコンパイルします (それらを使用しない疑似コードと見なします)。
于 2011-01-25T22:21:47.973 に答える