私は同時いじめっ子の全員でヒットによ視界、このセクションでは、我々は3つの原子悪の1に対する十字軍に進みます。
シーケンスセットなど、アトミック
動作の1つのまたは複数の特性原子呼ばCPUの実行中に中断されません。
私は操作があること、分割することができないことを理解アトミック。並行プログラミング環境では、長い糸の原子はこの一連の動作を開始し、どちらかのすべてを実行したり、すべての実行されていないようとして、実行の半分の存在が許されないことを意味しています。
我々は2つの側面から並行プログラミングやデータベースのトランザクションを比較してみました:
1、データベース内
原子のコンセプトは、このようなものです:トランザクションは、積分全体として扱われ、操作に含まれているいずれかのすべての実行さ、またはすべて実行できません。そして、実行時にエラーが発生した場合、トランザクションは、トランザクションが同じことを実行していないとして、トランザクションの開始前の状態にロールバックされます。(換言すれば、トランザクションがいずれか実行され、または全く実行されます)
2、並行プログラミングで
原子のコンセプトは、このようなものです:
- 理解する最初の:実装プロセス内のスレッドまたはプロセス、全くコンテキストスイッチが発生していないがあります。
- コンテキストスイッチは:別のプロセス/スレッド1つのプロセス/スレッドから(スイッチ前提は、CPUの使用権を取得することです)CPUスイッチを指します。
- 第二の理解:我々はスレッドまたは(不可分)操作を複数有し、実行のプロセスにおけるCPUの特性は、原子として知られ、中断されません。(割り込みが発生すると、実行時には、それはコンテキストスイッチが発生します)
原子の前述の説明から分かるように、2つの同時プログラミングとデータベースとの間の原子の概念が多少似ている:ストレスである、アトミック操作を中断することができません!!
アトミック操作ではなく、このような絵で表されます。
実行するB CPUスレッドを販売する一方を実行しているスレッド(まだ実行が完了していません)。このような動作は、それによってシステム全体の性能を向上させる、CPUの利用率を向上させるために多くのオペレーティングシステム、非常に短い時間のかかる犠牲スレッド切り替えを有し、この操作は、オペレーティング・システムは、「タイムスライス」スイッチと呼ばれています。
まず、原子問題の原因が発生します
共有変数の原子問題の原因は、スレッド間のコンテキスト切り替えで表示されます。序文で説明した原子の概念を通じ、と結論付けています。
それでは、私たちは一例で、原子問題を再現する必要があります。
まず、銀行口座のエンティティクラスの定義:
@Data
@AllArgsConstructor
public static class BankAccount {
private long balance;
public long deposit(long amount){
balance = balance + amount;
return balance;
}
}复制代码
共有銀行は預金業務、各1元預金を口座に続いて開いた複数のスレッド:
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.ArrayList;
/**
* @author :mmzsblog
* @description:并发中的原子性问题
* @date :2020/2/25 14:05
*/
public class AtomicDemo {
public static final int THREAD_COUNT = 100;
static BankAccount depositAccount = new BankAccount(0);
public static void main(String[] args) throws Exception {
ArrayList<Thread> threads = new ArrayList<>();
for (int i = 0; i < THREAD_COUNT; i++) {
Thread thread = new DepositThread();
thread.start();
threads.add(thread);
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Now the balance is " + depositAccount.getBalance() + "元");
}
static class DepositThread extends Thread {
@Override
public void run() {
for (int j = 0; j < 1000; j++) {
depositAccount.deposit(1); // 每次存款1元
}
}
}
}复制代码
上記の手順を複数回、結果はほとんど同じではありませんたびに実行、私たちは時折、望ましい結果を得るため100*1*1000=100000元
、以下は私が引用しているいくつかの実験の結果です:
理由は、上記の場合であります
balance = balance + amount;复制代码
このコードは、共有変数であるバランスそのアトミック操作ではありません。これは、マルチスレッド環境で中断することができます。だから、アトミック問題が裸で登場しました。図示のように:
当然のバランスがローカル変数であれば、それもマルチスレッド問題が発生した場合には表示されません(これはああ銀行口座にローカル変数を共有するためには適用されません、それ以外の場合は、変数を共有していないナンセンスに、ハハ、同等)、ローカル変数理由これは、現在のスレッドのプライベートです。変数jのループのために示されています。
以上の乾燥のための社会的関心番号「道路のJavaの学習」へようこそ!
しかし、その後も共有変数、小扁私たちはそれに対処する必要があるので、その後、アトミックの問題は、並行プログラミングのより深い理解があり、このような問題を許可しません。
第二に、によって引き起こされるアトミックコンテキストスイッチの問題を解決するために
2.1、ローカル変数を使用
ローカル変数のスコープは、ローカル変数は、総死亡と、ローカル変数とメソッドを放棄し、この方法は、実行されると言うことである内部の方法です。コールスタックは、非常に合理的ありでスタックフレームと総死亡とのメソッドなので、ローカル変数へのコールスタックです。実際には、ローカル変数は、実際にコールスタックに置かれます。
これは、各スレッドが独自のコールスタックを持っているので、各スレッド内のコールスタックに保存されたローカル変数、共有されていないので、自然に何の並行性の問題は存在しないです。要約:何の共有は、あなたは間違って行くことはできません。
ローカル変数は、千元の後、100スレッド各堆積物が、最後は0堆積物からのものである場合でも、ここでは、累積的ではない、結果を表示するように望んでいた失うことになります。したがって、この方法は現実的ではありません。
ここで使用されるように、単一のスレッドは、彼らが現在のシーンに適合しないので、同様に、原子性を保証することができますので、問題を解決することはできません。
2.2、アトミック性を確保するために来ます
Javaでは、読み出し動作と基本的な可変データ・タイプの割り当ては、アトミック操作です。
例えば、次のコード行:
// 原子性
a = true;
// 原子性
a = 5;
// 非原子性,分两步完成:
// 第1步读取b的值
// 第2步将b赋值给a
a = b;
// 非原子性,分三步完成:
// 第1步读取b的值
// 第2步将b值加2
// 第3步将结果赋值给a
a = b + 2;
// 非原子性,分三步完成:
// 第1步读取a的值
// 第2步将a值加1
// 第3步将结果赋值给a
a ++; 复制代码
2.3、同期
それは確かに可能ではない何かの原子へのすべてのJavaコード、コンピュータが時間を処理することができるものは、常に限られています。原子性を達成することができないときに、私たちは、このプロセスは、原子に沿ったものであるように思わせるための戦略を使用する必要があります。だから、そこに同期されました。
結果である可能性がアトミック操作を保証するために、操作の視認性を確保するために同期させることができます。
オブジェクト・インスタンス内で、synchronized aMethod(){}
同期メソッド複数のスレッドがオブジェクトを防止することができるアクセスします。
オブジェクトが同期メソッドにアクセスするための複数の同期方法限り、一つのスレッドを有する場合れ、他のスレッドは同時にいずれかの同期方法でオブジェクトにアクセスすることができません。
だから、私たちはここでしか同期セットに堆積させるために必要な方法は、原子性を保証することができるようになります。
private volatile long balance;
public synchronized long deposit(long amount){
balance = balance + amount; //1
return balance;
}复制代码
加えて、後に同期スレッドが同期堆積物の添加この方法が完了する前に実行されない場合、他のスレッドは、この修正されたコードの実行を同期させることができません。したがって、コード1の行の実装が中断された場合でも、他のスレッドが変数のバランスにアクセスすることができない;そうビューのマクロ観点から、次いで、最終的な結果は、正確さを確保することです。しかし、操作の途中で中断され、私たちは知りません。詳細については、CASの操作を見ることができます。
PS:なぜ変数バランスプラス揮発性のキーワードを:上記の変数は、我々は少し戸惑うかもしれないバランスのために?実際には、ここで揮発性のキーワードを追加する目的は、メインメモリから最新の値を読み取るためにあらゆる時間を、変数の可視性を確保するため、同期コード・ブロックへのアクセスを確保するためにバランスがあります。
したがって、ここでは
private volatile long balance;复制代码
また、同期の変更を置き換えることができます
private synchronized long balance;复制代码
それが私たちの中で最初の記事の可視性を保証することができるためと同時実行の奇妙な可視性、それは以前に導入されています。
2.4、ロックロック
public long deposit(long amount) {
readWriteLock.writeLock().lock();
try {
balance = balance + amount;
return balance;
} finally {
readWriteLock.writeLock().unlock();
}
}复制代码
ロックロック保証類似の原子理論と同期して、ここではそれらを繰り返すありません。
一部の読者は不思議に思うかもしれません、ここでのロックロックは、ロックの解除操作ですが、同期していないようです。実際には、Javaコンパイラが自動的に(コードロックロックの同期方法やブロックの前と後の修正が追加されます)と、アンロック、アンロック()、そうすることの利点は、(ロックをロックすることです)と、アンロック、アンロック()ペアで現れなければなりませんすべての後に、ロック解除を()ロックを解除するのを忘れたが、致命的なバグ(他のスレッドを意味するだけで死者を待つことができます)。
2.5アトミック操作タイプ
クラス定義は、結果の正確さを保証するために、原子プロパティを使用する場合は、次のようにエンティティクラスを変更する必要があります。
@Data
@AllArgsConstructor
public static class BankAccount {
private AtomicLong balance;
public long deposit(long amount) {
return balance.addAndGet(amount);
}
}复制代码
JDKクラスは、アトミック操作を保証するために、多くのアトミック操作を提供します。基本的なタイプの最も一般的な例:
AtomicBoolean
AtomicLong
AtomicDouble
AtomicInteger复制代码
これらのクラスは、CASメカニズムを使用して全体の割り当て原子は、最終的な結果の正しさを保証するため、中断されないことを、このメカニズム性を保証するアトミック動作の基礎となります。
同期と比較して、操作種別が対応する原子の原子顕微鏡を確実にするために、マクロからのアトミック性を保証するために同期されます。
2.5上記溶液を、それぞれ小さな操作は、これらのクラスAtomicLong変更動作として原子は、自分の操作がアトミックであるCRUD、アトミックです。
だから、ほんの少しの操作は全体構成を表していないとなっている各原子と一致していることの原子に沿ったものですか?
明らかではありません。
それはまだ、そのような全体のプロセスは、プロセスであるとして、スレッドの安全性の問題を、生成されます读取A-读取B-修改A-修改B-写入A-写入B
。変更はAは、その後、B Bは、読み出し動作を行うようになったスレッド、アトミックの営業損失を完了した場合ので、その後、問題の原子が存在します。
要するに、私はあなたのコードのすべてがスレッドセーフで、スレッドセーフなクラスを使用することを考えていません!あなたは、常に全体の原子からあなたのコードを確認する必要があります。次の例のように:
@NotThreadSafe
public class UnsafeFactorizer implements Servlet {
private final AtomicReference<BigInteger> lastNum = new AtomicReference<BigInteger>();
private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>();
@Override
public void service(ServletRequest request, ServletResponse response) {
BigInteger tmp = extractFromRequest(request);
if (tmp.equals(lastNum.get())) {
System.out.println(lastFactors.get());
} else {
BigInteger[] factors = factor(tmp);
lastNum.set(tmp);
lastFactors.set(factors);
System.out.println(factors);
}
}
}复制代码
それが原子を持つすべてのクラスではなく、それぞれの原子操作の間、動作します。言い換えれば、他にこのようなステートメントの実行中のスレッドlastNumber.set(tmp)
の後、そしておそらく他のスレッドではif文の実行lastFactorys.get()
方法を、その後、継続実行する前にAを通すlastFactors.set(factors)
方法更新factors
!
このロジックプロセスからは、スレッドの安全性の問題が発生しました。
それはの方法破壊する读取A-读取B-修改A-修改B-写入A-写入B
他のスレッドBを読み出し実行する書き込みA、Bの値の完了がBの値を読み取るために導いた後に書き込まれていない、このプロセス全体を。だから、原子が登場します。
まあ、それは私のコンテンツと原子少し理解する方法であり、我々は一般的に可視性、アトミックの問題を同時では一般的と彼らの共通の解決策を習得しますこれら二つの記事でまとめました。
遂に
ペースト原子の問題のいくつかの例は、しばしば見られます。
Qは:多くの場合、それはlong型の変数が32ビットマシン上のリスクと減算操作の存在によって複雑になる、最終的にはそれではないことを言った聞いたことありますか?
回答:足し算と引き算の同時リスクの存在は、32ビットマシン上の長い変数の操作のために右です。
理由は:スレッドスイッチアトミックの問題が発生します。
非揮発性long型とdoubleの変数のうち、8バイト、64ビット、32ビットマシンが32ビットのリードまたは変数を書き二つに操作をクリックする人々に、スレッドは32ビットの値が高い読み取ることができているです下位32ビットは、別のスレッドによって変更されています。公式にはlongdouble変数に最善をお勧めしますので、同時実行の問題を回避するために、揮発性または同期ロック同期として宣言されています。
参考記事:
- 1、Javaの並行プログラミングオタク時間の戦闘
- 2、HTTPS://juejin.im/post/5d52abd1e51d4561e6237124
- 3 HTTPS://www.cnblogs.com/54chensongxia/p/12073428.html
- 4、HTTPS://www.dazhuanlan.com/2019/10/04/5d972ff1e314a/
いいえ国民の関心を歓迎しない:Javaの学習パスを
個人のブログサイト:www.mmzsblog.cn