コードリーディングをしていると、AtomicReference
というクラスに当たったので、調べてみた。
AtmoicReference とは
リファレンスによると
アトミックに更新されるオブジェクトのリファレンス
との事。これだけだとわからないけど、コンカレントプログラミングを実施する際に、シェアするオブジェクトで更新したいものを保持しておく。syncronize
はコスト高だし、violatile
キーワード は書き込みを同時にされたら破綻する。
volatile
脱線するが、 volatile
キーワードの意図するところは、変数の参照を、CPUキャッシュではなく、メインメモリを参照することを保証することにある。このキーワードがない変数は、通常、CPUキャッシュを参照する。複数のCPUがあるシステムの場合、同じ値を参照するとは限らない。そこで、volatile
キーワードをつけると、実行しているCPUが違っても、メインメモリを参照するので、同じものをリファレンスすることが保証される。よって、「可視性の保証」ともいわれる。ちなみにvolatile
の意味は移り変わりやすい的な意味合い。
private volatile int counter = 0;
ちなみに、理解するのに、このサイトの説明は最高だった。 Java Concurrency
というサイトなので、ほかの記事も期待できそう。
AtomicReference
一方、AtmoicReference は、synchronized
でロックをがっつりかけることなく、複数のスレッドから参照、更新をするための仕組みです。
get()
AtomicReference<Integer> atomic = new AtomicReference<>();
System.out.println("atomic: "+ atomic.get());
atomic = new AtomicReference<>(10);
System.out.println("atomic: " + atomic.get());
result
atomic: null
atomic: 10
こんな感じで、型を指定しておくと、その値を保持したり、get()
メソッドで参照したりできます。コンストラクタはなにも渡さなければ null
で初期化されます。
set()
値のセットです。
atomic.set(100);
System.out.println("atomic: " + atomic.get());
result
atomic: 100
getAndUpdate()
通常は、set()
はそんなに使わないと思います。2つのスレッドで更新されたら、後で更新した値になってしまいます。それでよければそれでよいのですが。そこで、getAndUpdate()
のようなメソッドがあります。ラムダ式を渡して、現在の値をもとに更新をかけます。update
とget
の順番が逆のメソッドもあります。
UnaryOperator operator = (v) -> (Integer)v + 1;
System.out.println("atomic getAndUpdate: " + atomic.getAndUpdate(operator));
System.out.println("atomic after getAndUpdate: " + atomic.get());
System.out.println("atomic updateAndGet: " + atomic.updateAndGet(operator));
result
atomic getAndUpdate: 20
atomic after getAndUpdate: 21
atomic updateAndGet: 22
compareAndSet()
想定している値だった時のみ書き込むメソッドもあります。戻り値はboolean
なので、期待されている値だったかはわかります。これによって、ほかのスレッドで更新されていないことが保証されます。
# この時点で、値は 22
atomic.compareAndSet(23, 100);
System.out.println("compareAndSet maybe not updated: " + atomic.get());
result
compareAndSet maybe not updated: 22
ちなみに、weakCompareAndSet
というメソッドがあって、結果も同じだったのでなんだろう?と思ったら、Java8までは実装はcompareAndSet
と同じで、Java9 から、deprecated らしいので忘れてよさげです。