1. 概要

このチュートリアルでは、java.util.concurrent.atomicパッケージAtomicMarkableReferenceクラスの詳細について説明します。

次に、クラスのAPIメソッドについて説明し、AtomicMarkableReferenceクラスを実際に使用する方法を確認します。

2. 目的

AtomicMarkableReference は、Objectbooleanフラグの両方への参照をカプセル化する汎用クラスです。 これらの2つのフィールドは結合されており、は、一緒にまたは個別にアトミックに更新できます。

AtomicMarkableReference またである可能性があります ABA問題に対する可能な救済策。 

3. 実装

AtomicMarkableReferenceクラスの実装をさらに詳しく見てみましょう。

public class AtomicMarkableReference<V> {

    private static class Pair<T> {
        final T reference;
        final boolean mark;
        private Pair(T reference, boolean mark) {
            this.reference = reference;
            this.mark = mark;
        }
        static <T> Pair<T> of(T reference, boolean mark) {
            return new Pair<T>(reference, mark);
        }
    }

    private volatile Pair<V> pair;

    // ...
}

AtomicMarkableReference には、参照とフラグを保持する静的ネストクラスペアがあることに注意してください。

また、両方の変数がfinalであることがわかります。 その結果、これらの変数を変更するたびに、Pairクラスの新しいインスタンスが作成され、古いインスタンスが置き換えられます

4. メソッド

まず、 AtomicMarkableReference の有用性を発見するために、 EmployeePOJOを作成することから始めましょう。

class Employee {
    private int id;
    private String name;
    
    // constructor & getters & setters
}

これで、AtomicMarkableReferenceクラスのインスタンスを作成できます。

AtomicMarkableReference<Employee> employeeNode 
  = new AtomicMarkableReference<>(new Employee(123, "Mike"), true);

この例では、AtomicMarkableReferenceインスタンスが組織図のノードを表すと仮定します。 referenceからEmployeeクラスのインスタンスへのreference と、従業員がアクティブであるか会社を辞めたかを示すマークの2つの変数を保持しています。

AtomicMarkableReference には、一方または両方のフィールドを更新または取得するためのいくつかのメソッドが付属しています。 これらのメソッドを1つずつ見ていきましょう。

4.1. getReference()

getReference メソッドを使用して、reference変数の現在の値を返します。

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);

Assertions.assertEquals(employee, employeeNode.getReference());

4.2. isMarked()

mark 変数の値を取得するには、isMarkedメソッドを呼び出す必要があります。

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);

Assertions.assertTrue(employeeNode.isMarked());

4.3. get()

次に、現在のreferenceと現在のmarkの両方を取得する場合は、getメソッドを使用します。 マークを取得するには、少なくとも1つのサイズのブール配列をパラメーターとして送信する必要があります。これにより、ブール変数の現在の値がインデックス0に格納されます。 同時に、このメソッドはreferenceの現在の値を返します。

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);

boolean[] markHolder = new boolean[1];
Employee currentEmployee = employeeNode.get(markHolder);

Assertions.assertEquals(employee, currentEmployee);
Assertions.assertTrue(markHolder[0]);

referenceフィールドとmarkフィールドの両方を取得するこの方法は、内部の Pair クラスが呼び出し元に公開されていないため、少し奇妙です。

JavaにはジェネリックがありませんペアパブリックAPIのクラス。 これの主な理由は、個別のタイプを作成する代わりに、それを使いすぎたくなるかもしれないということです。

4.4. set()

referenceフィールドとmarkフィールドの両方を無条件に更新する場合は、setメソッドを使用する必要があります。 パラメータとして送信される値の少なくとも1つが異なる場合、referencemarkが更新されます。

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);

Employee newEmployee = new Employee(124, "John");
employeeNode.set(newEmployee, false);
        
Assertions.assertEquals(newEmployee, employeeNode.getReference());
Assertions.assertFalse(employeeNode.isMarked());

4.5. compareAndSet()

次に、 compareAndSet メソッドは、referencemarkの両方を、現在の参照が期待される参照と等しい場合、指定された更新値に更新します。現在のマークは期待されるマークと同じです。

次に、 compareAndSet を使用して、referenceフィールドとmarkフィールドの両方を更新する方法を見てみましょう。

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);
Employee newEmployee = new Employee(124, "John");

Assertions.assertTrue(employeeNode.compareAndSet(employee, newEmployee, true, false));
Assertions.assertEquals(newEmployee, employeeNode.getReference());
Assertions.assertFalse(employeeNode.isMarked());

また、 compareAndSet メソッドを呼び出すと、フィールドが更新された場合は true を取得し、更新が失敗した場合はfalseを取得します。

4.6. weakCompareAndSet()

weakCompareAndSetメソッドは、compareAndSetメソッドのより弱いバージョンである必要があります。つまり、compareAndSetのように強力なメモリオーダリング保証を提供しません。 また、ハードウェアレベルで排他的アクセスを取得できない場合もあります。

これは、weakCompareAndSetメソッドの仕様です。 でも、 現在、weakCompareAndSetは、内部でcompareAndSetメソッドを呼び出すだけです。 したがって、それらは同じ強力な実装を持っています。

現在、これら2つのメソッドの実装は同じですが、仕様に基づいて使用する必要があります。 したがって、weakCompareAndSetを弱いアトミックと見なす必要があります。

弱いアトミックは、一部のプラットフォームおよび状況によってはより安価になる可能性があります。 たとえば、ループで compareAndSet を実行する場合は、より弱いバージョンを使用することをお勧めします。 この場合、ループ内にあるときに最終的に状態を更新するため、誤った障害がプログラムの正確性に影響を与えることはありません。

つまり、弱いアトミックは特定のユースケースで役立つ可能性があり、その結果、考えられるすべてのシナリオに適用できるわけではありません。 したがって、疑わしい場合は、より強力なcompareAndSetを選択してください。

4.7. attemptMark()

最後に、attemptMarkメソッドがあります。 現在のreferenceがパラメータとして送信される予想されるreferenceと等しいかどうかをチェックします。 それらが一致する場合、マークの値を指定された更新された値にアトミックに設定します。

Employee employee = new Employee(123, "Mike");
AtomicMarkableReference<Employee> employeeNode = new AtomicMarkableReference<>(employee, true);

Assertions.assertTrue(employeeNode.attemptMark(employee, false));
Assertions.assertFalse(employeeNode.isMarked());

予想される参照と現在の参照が等しい場合でも、このメソッドが誤って失敗する可能性があることに注意することが重要です。 結果として、メソッドの実行によって返されるブール値に注意を払う必要があります

結果は、 markが正常に更新された場合はtrue、それ以外の場合はfalseになります。 ただし、現在のreferenceが予想されるreferenceと等しい場合に繰り返し呼び出すと、markの値が変更されます。 したがって、whileループ構造内でこのメソッドを使用することをお勧めします。

この失敗は、attemptMark メソッドがフィールドを更新するために使用する基礎となるコンペアアンドスワップ(CAS)アルゴリズムの結果として発生する可能性があります。 CASを使用して同じ値を更新しようとしているスレッドが複数ある場合、そのうちの1つは値を変更でき、他のスレッドには更新が失敗したことが通知されます。

5. 結論

このクイックガイドでは、AtomicMarkableReferenceクラスがどのように実装されているかを学びました。 さらに、クラスのパブリックAPIメソッドを使用して、そのプロパティをアトミックに更新する方法を発見しました。

いつものように、GitHubより多くの例と記事の完全なソースコードを入手できます。