回答:
アトミック参照は、モニターベースの同期が適切でない、参照に対して単純なアトミック(つまり、スレッドセーフ、非自明)操作を実行する必要がある設定で使用する必要があります。オブジェクトの状態が最後に確認したとおりに残っている場合にのみ、特定のフィールドかどうかを確認するとします。
AtomicReference<Object> cache = new AtomicReference<Object>();
Object cachedValue = new Object();
cache.set(cachedValue);
//... time passes ...
Object cachedValueToUpdate = cache.get();
//... do some work to transform cachedValueToUpdate into a new version
Object newValue = someFunctionOfOld(cachedValueToUpdate);
boolean success = cache.compareAndSet(cachedValue,cachedValueToUpdate);
アトミック参照セマンティクスにより、cache
オブジェクトをスレッド間で共有している場合でも、を使用せずにこれを行うことができますsynchronized
。一般的には、何をしているのかを理解していない限り、シンクロナイザーやjava.util.concurrent
フレームワークを使用する方が無難Atomic*
です。
このトピックを紹介する2つの優れたデッドツリー参照:
(これは常に真となっている場合、私は知りません)なお、参照(すなわち割り当て=
自体はアトミックである)(更新プリミティブのような64ビットの種類をlong
かdouble
アトミックではないかもしれない。しかし、更新、参照することは、それは64ビットだ場合でも、常にアトミックです)を明示的に使用せずにAtomic*
。Java言語仕様3ed、セクション17.7を
参照してください。
AtomicReference
は、変数をマークする必要がありvolatile
ながらので、ランタイムはその参照の割り当てを保証アトミックで、コンパイラは変数が他のスレッドによって変更されていなかったという仮定の下での最適化を行うことができます。
AtomicReference
「使用していない場合」と言ったことに注意してください。あなたがそれを使用しているなら、私のアドバイスは反対の方向に行きfinal
、コンパイラーがそれに応じて最適化できるようにそれをマークすることです。
アトミック参照は、複数のスレッド間で不変オブジェクトの状態を共有および変更する必要がある場合に使用するのが理想的です。これは非常に高密度のステートメントなので、少し詳しく説明します。
まず、不変オブジェクトは、構築後に実質的に変更されないオブジェクトです。不変オブジェクトのメソッドは、同じクラスの新しいインスタンスを返すことがよくあります。いくつかの例としては、LongとDoubleのラッパークラス、およびStringが含まれます。(JVMでの並行プログラミングによると不変オブジェクトは最新の並行性の重要な部分です)。
次に、その共有値を共有するために、AtomicReferenceが揮発性オブジェクトよりも優れている理由。簡単なコード例で違いを示します。
volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
synchronized(lock){
sharedValue=sharedValue+"something to add";
}
}
現在の値に基づいて揮発性フィールドによって参照される文字列を変更するたびに、まずそのオブジェクトのロックを取得する必要があります。これにより、その間に他のスレッドが入り、新しい文字列連結の途中で値が変更されるのを防ぎます。その後、スレッドが再開すると、他のスレッドの作業を妨害します。しかし、正直に言って、そのコードは機能し、見た目はきれいで、ほとんどの人を幸せにします。
わずかな問題。遅いです。特に、そのロックオブジェクトの競合が多い場合。ほとんどのロックにはOSシステムコールが必要であり、スレッドはブロックされ、CPUからコンテキストが切り替えられて他のプロセスに代わるからです。
他のオプションは、AtomicRefrenceを使用することです。
public static AtomicReference<String> shared = new AtomicReference<>();
String init="Inital Value";
shared.set(init);
//now we will modify that value
boolean success=false;
while(!success){
String prevValue=shared.get();
// do all the work you need to
String newValue=shared.get()+"lets add something";
// Compare and set
success=shared.compareAndSet(prevValue,newValue);
}
なぜこれが良いのでしょうか?正直なところ、そのコードは以前より少しクリーンではありません。しかし、AtomicRefrenceの内部で起こる重要なことがあり、それは比較と交換です。切り替えを発生させるのは、OS呼び出しではなく、単一のCPU命令です。これは、CPU上の単一の命令です。また、ロックがないため、ロックが実行された場合のコンテキストスイッチがなく、さらに時間を節約できます。
キャッチは、AtomicReferencesの場合、これは.equals()呼び出しを使用せず、代わりに期待値の==比較を使用します。したがって、ループ内のgetから返された実際のオブジェクトが期待されるものであることを確認してください。
worked
同じセマンティクスを取得するには、ループする必要があります。
AtomicReferenceの使用例を次に示します。
数値範囲として機能し、個々のAtmomicInteger変数を使用して、数値の下限と上限を維持するこのクラスを考えます。
public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
// Warning -- unsafe check-then-act
if (i > upper.get())
throw new IllegalArgumentException(
"can't set lower to " + i + " > upper");
lower.set(i);
}
public void setUpper(int i) {
// Warning -- unsafe check-then-act
if (i < lower.get())
throw new IllegalArgumentException(
"can't set upper to " + i + " < lower");
upper.set(i);
}
public boolean isInRange(int i) {
return (i >= lower.get() && i <= upper.get());
}
}
setLowerとsetUpperはどちらもcheck-then-actシーケンスですが、アトミックにするために十分なロックを使用していません。数値の範囲が(0、10)で、1つのスレッドがsetLower(5)を呼び出し、別のスレッドがsetUpper(4)を呼び出す場合、不運なタイミングで、両方がセッターのチェックに合格し、両方の変更が適用されます。その結果、範囲は無効な状態(5、4)を保持するようになります。したがって、基になるAtomicIntegerはスレッドセーフですが、複合クラスはそうではありません。これは、上限と下限に個別のAtomicIntegerを使用する代わりに、AtomicReferenceを使用することで修正できます。
public class CasNumberRange {
// Immutable
private static class IntPair {
final int lower; // Invariant: lower <= upper
final int upper;
private IntPair(int lower, int upper) {
this.lower = lower;
this.upper = upper;
}
}
private final AtomicReference<IntPair> values =
new AtomicReference<IntPair>(new IntPair(0, 0));
public int getLower() {
return values.get().lower;
}
public void setLower(int lower) {
while (true) {
IntPair oldv = values.get();
if (lower > oldv.upper)
throw new IllegalArgumentException(
"Can't set lower to " + lower + " > upper");
IntPair newv = new IntPair(lower, oldv.upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
public int getUpper() {
return values.get().upper;
}
public void setUpper(int upper) {
while (true) {
IntPair oldv = values.get();
if (upper < oldv.lower)
throw new IllegalArgumentException(
"Can't set upper to " + upper + " < lower");
IntPair newv = new IntPair(oldv.lower, upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
}
これは非常に単純な使用例であり、スレッドの安全性とは何の関係もありません。
ラムダ呼び出し間でオブジェクトを共有するにAtomicReference
は、これがオプションです:
public void doSomethingUsingLambdas() {
AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();
soSomethingThatTakesALambda(() -> {
yourObjectRef.set(youObject);
});
soSomethingElseThatTakesALambda(() -> {
YourObject yourObject = yourObjectRef.get();
});
}
これが良いデザインだとか言っているわけではありませんが(それはほんの些細な例です)、ラムダ呼び出し間でオブジェクトを共有する必要がある場合は、 AtomicReference
はオプションです。
実際、参照を保持する任意のオブジェクトを使用でき、アイテムが1つしかないコレクションも使用できます。ただし、AtomicReferenceは最適です。
あまり話さない。私の尊敬する仲間の友人はすでに貴重な意見を述べています。このブログの最後にある本格的な実行コードは、混乱を取り除くはずです。それはマルチスレッドのシナリオで映画の座席予約の小さなプログラムについてです。
いくつかの重要な基本的な事実は次のとおりです。1>異なるスレッドは、ヒープスペース内のインスタンスおよび静的メンバー変数のみと競合できます。2>揮発性の読み取りまたは書き込みは完全にアトミックであり、以前はシリアル化され、メモリからのみ行われます。これを言うことで、すべての読み取りがメモリ内の以前の書き込みに続くことを意味します。そして、書き込みはメモリからの前回の読み取りに従います。そのため、volatileを使用するスレッドは常に最新の値を参照します。 AtomicReferenceはこの揮発性のプロパティを使用します。
以下は、AtomicReferenceのソースコードの一部です。AtomicReferenceはオブジェクト参照を参照します。この参照は、以下のようにAtomicReferenceインスタンスの揮発性メンバー変数です。
private volatile V value;
get()は、変数の最新の値を返します(揮発性変数が「前に起こる」方法で行うように)。
public final V get()
以下は、AtomicReferenceの最も重要なメソッドです。
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
compareAndSet(expect、update)メソッドは、Javaの安全でないクラスのcompareAndSwapObject()メソッドを呼び出します。unsafeのこのメソッド呼び出しは、ネイティブの呼び出しを呼び出します。これは、プロセッサへの単一の命令を呼び出します。「expect」と「update」はそれぞれオブジェクトを参照します。
AtomicReferenceインスタンスメンバー変数「値」が同じオブジェクトを参照する場合に限り、「期待」によって「更新」がこのインスタンス変数に割り当てられ、「true」が返されます。それ以外の場合は、falseが返されます。すべてがアトミックに行われます。他のスレッドがその間に傍受することはできません。これはシングルプロセッサ操作(最新のコンピューターアーキテクチャの魔法)であるため、同期ブロックを使用するよりも高速であることがよくあります。だが、複数の変数をアトミックに更新する必要がある場合、AtomicReferenceは役に立ちません。
Eclipseで実行できる本格的な実行コードを追加したいと思います。それは多くの混乱を取り除くでしょう。ここでは22人のユーザー(MyThスレッド)が20席を予約しようとしています。以下は、完全なコードが後に続くコードスニペットです。
22人のユーザーが20席を予約しようとしているコードスニペット。
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
以下は実行中の完全なコードです。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class Solution {
static List<AtomicReference<Integer>> seats;// Movie seats numbered as per
// list index
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
seats = new ArrayList<>();
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
for (Thread t : ths) {
t.join();
}
for (AtomicReference<Integer> seat : seats) {
System.out.print(" " + seat.get());
}
}
/**
* id is the id of the user
*
* @author sankbane
*
*/
static class MyTh extends Thread {// each thread is a user
static AtomicInteger full = new AtomicInteger(0);
List<AtomicReference<Integer>> l;//seats
int id;//id of the users
int seats;
public MyTh(List<AtomicReference<Integer>> list, int userId) {
l = list;
this.id = userId;
seats = list.size();
}
@Override
public void run() {
boolean reserved = false;
try {
while (!reserved && full.get() < seats) {
Thread.sleep(50);
int r = ThreadLocalRandom.current().nextInt(0, seats);// excludes
// seats
//
AtomicReference<Integer> el = l.get(r);
reserved = el.compareAndSet(null, id);// null means no user
// has reserved this
// seat
if (reserved)
full.getAndIncrement();
}
if (!reserved && full.get() == seats)
System.out.println("user " + id + " did not get a seat");
} catch (InterruptedException ie) {
// log it
}
}
}
}
AtomicReferenceはいつ使用しますか?
AtomicReferenceは、同期を使用せずに変数値をアトミックに更新する柔軟な方法です。
AtomicReference
単一の変数でロックフリーのスレッドセーフプログラミングをサポートします。
高レベルの並行 APIでスレッドセーフを実現するには、いくつかの方法があります。原子変数は、複数のオプションの1つです。
Lock
オブジェクトは、多くの同時アプリケーションを簡略化するロックイディオムをサポートしています。
Executors
スレッドを起動および管理するための高レベルAPIを定義します。java.util.concurrentによって提供されるエグゼキューター実装は、大規模なアプリケーションに適したスレッドプール管理を提供します。
並行コレクションを使用すると、大量のデータのコレクションを管理しやすくなり、同期の必要性を大幅に減らすことができます。
アトミック変数には、同期を最小限に抑え、メモリの整合性エラーを回避するのに役立つ機能があります。
AtomicReferenceを使用する必要がある簡単な例を示します。
のサンプルコードAtomicReference
:
String initialReference = "value 1";
AtomicReference<String> someRef =
new AtomicReference<String>(initialReference);
String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);
すべてのマルチスレッドプログラムでオブジェクトを作成する必要がありますか?
AtomicReference
すべてのマルチスレッドプログラムで使用する必要はありません。
単一の変数を保護する場合は、を使用しますAtomicReference
。コードブロックを保護する場合は、Lock
/ synchronized
などの他の構成を使用します。
もう1つの簡単な例は、セッションオブジェクトでセーフスレッド変更を行うことです。
public PlayerScore getHighScore() {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
return holder.get();
}
public void updateHighScore(PlayerScore newScore) {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
while (true) {
HighScore old = holder.get();
if (old.score >= newScore.score)
break;
else if (holder.compareAndSet(old, newScore))
break;
}
}
出典:http : //www.ibm.com/developerworks/library/j-jtp09238/index.html