マルチスレッド2:Java原子変数(java.util.concurrent.atomic.*)
3563 ワード
同時制御のないカウンタ:
正常に動作している場合がありますが、次のような結果が出ることがあります.
これは明らかに予想された結果とは異なり、まずjavap-verboseコマンドでこのクラスを分析し、バイトコードレベルでは++countは仮想マシンが次の5つのバイトコードコマンドを順次実行することに等しい(実行期間の最適化を考慮しない)
Thread-3スレッドがgetstatic命令を実行する場合、Thread-2スレッドはまだiadd命令に実行されていないため、Thread-3スレッドが取得した初期静的ドメインcountの値はThread-2スレッドと同様に2である
本質的な原因は++countは1行のコードであるが,この過程は原子操作ではない.
このタイプの原子操作を保証するにはjava.util.concurrent.atomicパッケージのクラスを使用します.
例は次のとおりです.
ソースコードでどのように実装されているかを見てみましょう
楽観的なロックと似ているのではないでしょうか.結果が予想される結果に一致した場合は、結果を返します.そうしないと、再試行を続け、同期は行われず、セキュリティとパフォーマンスを両立させます.
JAva.util.concurrent.atomicパッケージには多くのクラスがあります.これらのクラスを使用すると、競合条件の発生を回避するために、これらのクラスに対する「取得-更新」操作が原子的であることを保証できます.
public class Counter implements Runnable {
private static int count;
public void run() {
System.out.println(Thread.currentThread().getName()
+ ":" + (++count));
}
public static void main(String[] args){
Counter counter = new Counter();
Thread t1 = new Thread(counter);
Thread t2 = new Thread(counter);
Thread t3 = new Thread(counter);
Thread t4 = new Thread(counter);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
正常に動作している場合がありますが、次のような結果が出ることがあります.
Thread-1:2
Thread-0:1
Thread-2:3
Thread-3:3
これは明らかに予想された結果とは異なり、まずjavap-verboseコマンドでこのクラスを分析し、バイトコードレベルでは++countは仮想マシンが次の5つのバイトコードコマンドを順次実行することに等しい(実行期間の最適化を考慮しない)
getstatic ,
iconst_1 int 1
iadd int
dup
putstatic
Thread-3スレッドがgetstatic命令を実行する場合、Thread-2スレッドはまだiadd命令に実行されていないため、Thread-3スレッドが取得した初期静的ドメインcountの値はThread-2スレッドと同様に2である
本質的な原因は++countは1行のコードであるが,この過程は原子操作ではない.
このタイプの原子操作を保証するにはjava.util.concurrent.atomicパッケージのクラスを使用します.
java.util.concurrent.atomic
, 。
例は次のとおりです.
public class Counter implements Runnable {
private final AtomicInteger count = new AtomicInteger(0);
public void run() {
System.out.println(Thread.currentThread().getName()
+ ":" + count.incrementAndGet());
}
public static void main(String[] args){
Counter counter = new Counter();
Thread t1 = new Thread(counter);
Thread t2 = new Thread(counter);
Thread t3 = new Thread(counter);
Thread t4 = new Thread(counter);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
ソースコードでどのように実装されているかを見てみましょう
private volatile int value;
public AtomicInteger(int initialValue) {
value = initialValue;
}
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
/**
* Atomically sets the value to the given updated value
* if the current value {@code ==} the expected value.
*
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final int get() {
return value;
}
楽観的なロックと似ているのではないでしょうか.結果が予想される結果に一致した場合は、結果を返します.そうしないと、再試行を続け、同期は行われず、セキュリティとパフォーマンスを両立させます.
JAva.util.concurrent.atomicパッケージには多くのクラスがあります.これらのクラスを使用すると、競合条件の発生を回避するために、これらのクラスに対する「取得-更新」操作が原子的であることを保証できます.
AtomicBoolean boolean 。
AtomicInteger int 。
AtomicIntegerArray int 。
AtomicIntegerFieldUpdater<T> , volatile int 。
AtomicLong long 。
AtomicLongArray long 。
AtomicLongFieldUpdater<T> , volatile long 。
AtomicMarkableReference<V> AtomicMarkableReference , 。
AtomicReference<V> 。
AtomicReferenceArray<E> 。
AtomicReferenceFieldUpdater<T,V> , volatile 。
AtomicStampedReference<V> AtomicStampedReference “ ” , 。