Javaの並行プログラミング・ノート(3) - スレッドの安全性
スレッドの安全性:
複数のスレッドが関係なくクラスを、アクセスするとスケジューリング実行環境の種類を使用するか、または、これらの処理が交互に行われるか、および呼び出し側のコードに追加の同期的または相乗を必要としない、クラスが正しいを表示することができます動作は、このクラスを呼び出すことは、スレッドセーフです。
スレッドの安全性は、三つの側面に反映されます。
- アトミック:それを操作するときに排他的アクセス、一つのスレッドだけを提供
- 可視性:メインメモリは他のスレッドまでの時間で観察することができるスレッドを変更
- 順序:スレッドの他のスレッド観察命令実行順序、命令並べ替え、一般的な観察無秩序があるからです。
原子性:アトミックパケット
使用のAtomicIntegerは、変数のアトミック動作を保証します
public class CountExample2 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count.get());
}
private static void add() {
count.incrementAndGet(); //相当于++x;
// count.getAndIncrement(); //相当于x++
}
}
原理:incrementAndGetののAtomicInteger()メソッドが安全でないクラスの内部で使用されています
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
実現getAndAddIntのポイントへの深い表情を続行します。
//
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
ここで最も重要な方法は次のとおりです。メソッド背後のjavaでcompareAndSwapIntは()、これは、Javaで実装されていません。
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
パラメータの説明:
オブジェクトVAR1:例えば、この場合のように、オブジェクト操作は、このObectはのAtomicIntegerカウントです。
長いVAR2:オブジェクトの現在の値。
INTのVAR4:例えば、この場合のように、現在のオブジェクトの値を大きくする+1操作を行い、その後VAR4は1です。
INTのVAR5:底を呼び出すことによって得られる値は、操作上他のスレッド場合、この値はVAR2に等しくなければなりません
getAndAddInt()メソッドcompareAndSwapInt()メソッドについて説明行う:オブジェクトVAR1、VAR2ため底VAR5から得られた値が同じである場合、場合、実装VAR5 + VAR4。
さらに説明:カウンタの現在の値は、現在のスレッドの値がワーキングメモリ内のスレッドの値を所属され、基礎となる買収の値は、ワーキングメモリの値とメインメモリの値があるだけで、メインメモリの中央値であります同時に、それは変更することができます。
AtomicLong、LongAdder
上記の例では、AtomicLongを置き換えるためのAtomicIntegerは、方法全体はまだスレッドセーフです。
第二の方法はLongAdderを使用することです:
public class AtomicExample3 {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static LongAdder count = new LongAdder();
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
count.increment();
}
}
AtomicLongとLongAdder比較:
AtomicLong:このクラスは、常に変更が、成功の確率が大幅に変更、競争力のある状況で失敗の大きな可能性を修正する競争力の弱い状況で成功するまでの目標値を変更するために無限ループで基本となる実装しようとしています、それは、この場合のパフォーマンスに有害です。
LongAdderは:ロングのため、ダブルタイプ値JVMは、それらを読み取りおよび書き込み操作を、LongAdderは、その後、得られたアレイに追加されます。この動作原理に応じて値の配列に分割するために読み取り、64ビット、32ビットを分割し、書き込み動作を可能にしますそして、スピン圧力平衡操作により、その性能は比較的良好です
使用シナリオの選択:優先性の高い同時実行数のシナリオで使用されるLongAdder、他の状況がAtomicLongを使用
AtomicBoolean
メソッド基礎となる実装は次のようになります。
public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
この方法は、論理値を実行するコードのブロックを指します。
(この場合は一度だけ行われ、複数のスレッドのコードの特定の部分を実証する)ケースを使用します。
public class AtomicExample6 {
private static AtomicBoolean isHappened = new AtomicBoolean(false);
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
test();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("isHappened:{}", isHappened.get());
}
private static void test() {
if (isHappened.compareAndSet(false, true)) {
log.info("execute");
}
}
}
AtomicReference、AtomicReferenceFieldUpdater
AtomicReferenceの使用例:
public class AtomicExample4 {
private static AtomicReference<Integer> count = new AtomicReference<>(0);
public static void main(String[] args) {
count.compareAndSet(0, 2); // 2
count.compareAndSet(0, 1); // no
count.compareAndSet(1, 3); // no
count.compareAndSet(2, 4); // 4
count.compareAndSet(3, 5); // no
log.info("count:{}", count.get()); //4
}
}
AtomicReferenceFieldUpdaterの使用例:
public class AtomicExample5 {
private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");
@Getter
public volatile int count = 100;
public static void main(String[] args) {
AtomicExample5 example5 = new AtomicExample5();
if (updater.compareAndSet(example5, 100, 120)) {
log.info("update success 1, {}", example5.getCount());
}
if (updater.compareAndSet(example5, 100, 120)) {
log.info("update success 2, {}", example5.getCount());
} else {
log.info("update failed, {}", example5.getCount());
}
}
}
AtomicStampedReference:CASはABAの問題を解決
ABA問題:CAS動作、Bの値に変数、他のスレッドが、所望の値を使用してスレッド、バックAにこの時間を変更し、現在の変数と比較され、変数Aが見つからないCAS次いで、変更値の交換作業。
解決策:すべての変数の更新、バージョン番号+1
コアクラス:
AtomicStampedReference
コアの方法のcompareAndSet()
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
Pair<V> current = pair;
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
AtomicLongArray
このクラスは、メンテナンスの配列です
AtomicLongと比較してこのクラスには、もう一つの方法は、私たちはインデックス値を指定することができます。
アトミック - ロック
- 同期:JVMに依存してロックを達成するために
- ロック:CPUに依存する特殊な命令、達成するためのコード、ReenteantLock
同期:
- 変更されたブロック:コードブレース、オブジェクトの呼び出しに作用します
修正方法:オブジェクトの呼び出しに作用するのプロセス全体
- 変更された静的メソッド:全体の静的メソッド、すべてのオブジェクトに作用します
すべてのオブジェクトに作用する変性タイプ、括弧の部分、
不可分性 - 比較
同期:ロックは競争力の弱い、良い読みやすさのために中断することはできません
ロック:割り込みロックを、マニホールドと、とき熾烈な競争の正常性を維持するために
アトミック:ロックのパフォーマンスよりも優れて通常時に熾烈な競争を維持するために、値のみを同期
可視
スレッド間の見えない共有変数を原因
- クロススレッドの実行
- 結合クロススレッドが並べ替えを実行します
- 共有変数の更新プログラムの値は、ワーキングメモリとメインメモリの間で最新の状態にはありません
可視性-同期
JMM約2同期の条項:
- スレッドが共有変数の値のロックを解除する前に、メインメモリに最新の更新でなければなりません
- スレッドロックは、(:ロックとロック解除が同じロックであることに注意してください)変数のワーキングメモリの共有値をクリアするため、共有変数を使用する際に、メインメモリから再読み込み最新の値にする必要があります
可視性-揮発性
メモリバリアは、並べ替え、最適化の禁止によって達成された場合
- volatile変数の書き込み動作は、バリアが書き込み動作のストア命令を追加する場合は、ローカルメモリは、メインリフレッシュの値内の変数を共有しました
- volatileb変数は操作を読んだとき、負荷は、読み出し動作の前にバリア命令をメインメモリから共有変数の読み出しを追加します
アトミックを持たないvolatileキーワード
適切なシーン:
- 操作は変数と現在の値に依存しない書きます。
- この変数は、他の変数と不変に含まれていません。
揮発性ステータスフラグ量のためにこのように特に適して
整頓
Javaのメモリモデルは、編集者とプロセッサの命令が並べ替えることができるように、しかし、リオーダリング処理はシングルスレッドプログラムの実施には影響しませんが、それは、複数のスレッドの同時実行の正しさに影響を与えます。
通常の状況下では、次の3つのキーワードを注文することによって確保することができます。
- 揮発性
- 同期
- ロック
たまたま、前の原則
二つの操作の実行順序は、原則から推定することができない場合は事前発生、その後、我々は彼らの秩序、仮想マシンが任意にそれらを並べ替えることができます保証することはできません。
つまり、これらの規則の規定以下のシーンに加えて、他のシーンで、仮想マシンを並べ替えることができます。
- プログラムシーケンスルール:スレッド、操作の前にコードブックエディトリアルの順序に応じて、後で書き込み動作で発生します
- ルールをブロック:ロック解除操作がロック操作で最初の顔ロックで発生した後、
- volatile変数のルール:変数事前に操作を書く読み出し動作の後に、この変数の顔に発生しました
ルールを渡す:操作が先手順Bを発生し、そしてBが最初の操作は、Cを発生して動作する場合、動作は前述の操作で得られるCを発生
- スレッド開始規則:開始Threadオブジェクト()メソッドは、最初にすべてのアクションは、このスレッドで行われます
- 原則割り込みスレッド:呼び出しスレッドに割り込み()メソッドは、最初のイベントを中断し、中断スレッドコード検出で発生発生しました。
- ルールの最後スレッド:すべての操作では、スレッドが終了検出スレッドの前に場所を取る、我々はThread.join()メソッドで終了することができ、Thread.isAlive()の戻り値は、スレッドが終了したことを検出します。
ルールオブジェクトの終了:オブジェクトの初期化は、ファイナライズ()メソッドの開始におけるその最初の発生を完了