序文
「見た誰も高い同時実行-問題の並行プログラミング奇妙なソース、」子供の靴並行プログラミングの根本原因の用紙バグが一定の理解を持って、はい、それはありません:キャッシングに起因する可視性の問題、スレッドの切り替えをもたらしますアトミック問題や秩序コンパイラの最適化問題をもたらします。問題は、並行プログラミング言語に関連するすべてが遭遇する、実際よりちょうどJava言語が発生する可能性があります。
-さんは、Javaを見て、この時点で、スレッドの可視性と秩序の問題を解決する方法です、私はJavaのコア技術、言及する必要があります見てみましょうJavaのメモリモデルを。
並行プログラムを書くことに問題がある場合は、それはこの時点では、適切な委託することによって問題を解決することは困難である、あなたは完全に理解し、Javaのメモリモデルを習得する場合、ライン、この時点でコードの行をチェックする必要がある、あなたはすぐに分析することが可能となりますどこに問題がある見つけます。
Javaのメモリモデルとは何ですか?
今、私たちは、すべての可視性の問題、問題の順序につながったコンパイラの最適化にキャッシュリードを理解しています。それは、視認性を解決するための最も直接的な方法であると秩序の問題があるためにキャッシュとコンパイラの最適化を無効にします。あなたは、単に無効にキャッシングおよびコンパイラの最適化は、我々はいわゆる高並列プログラムのパフォーマンスが高くならないことを書いた場合は、どこへ行きます!そして、シングルスレッドのプログラムであってもパフォーマンスは変わりありません!
完全にキャッシュとコンパイラ最適化を無効にしないのであれば、どのような問題を解決するため視認性とそれの秩序?実際には、合理的な解決策はあるべきキャッシュを無効にし、コンパイラの最適化に必要に従って。それは(ここでは理解することに焦点を当てる必要があります)高並列プログラムを書くために、開発者の要件に応じて合理的なキャッシュを無効とコンパイラ最適化することができます。だから、いわば、可視性と秩序の問題を解決するために、JavaのJavaへのプログラマは唯一の無効化、キャッシングおよびコンパイラ最適化手法に、必要に応じて提供する必要がありますすることができます。
Javaのメモリモデルは、非常に複雑な仕様、Javaのメモリモデルに関する多くのオンラインの記事ですが、ほとんどの言うには、理論、それはより多くのナンセンスになるという理論です。ここで、私はそれらの理論があいまいすぎたJavaメモリモデルを紹介しません。実際には、Java開発者として、我々は唯一のJavaメモリモデルを理解するためにプログラマーの視点に立つ必要があります。そんなに理解しやすく、Javaの開発者はJavaのメモリモデルの視点に立っとして理解することができます:Java仮想マシン(JVM)のJavaのメモリモデルの仕様はどのようキャッシュを無効にし、オンデマンドのコンパイラの最適化方法を提供します。
いくつかの発言は具体的には、これらのメソッドが含まれます:、揮発同期して、最終的なキーワードだけでなく、ルールは事前発生のJavaメモリモデル。
なぜスレッド見えることを保証するために、揮発性のでしょうか?
volatileキーワードは、Javaに固有ではないが、C言語でvolatileキーワードは、このキーワードの最も原始的な感覚が無効にCPUのキャッシュにあります。
例えば、我々は以下に示すように、プログラム中で変数を宣言するために揮発性のキーワードを使用します。
volatile int count = 0
この時点では、Javaは、この変数に読み込み、書き込み、あなたは、CPUのキャッシュを使用することはできません、あなたがメモリから読み出され、書き込みしなければなりません。
以下に示すように次に、私たちは、スニペットに沿って見てください。
実施例[A]
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
//x的值是多少呢?
}
}
}
上記の例から:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalWrong
ここで、揮発性メモリに応じて実行スレッドAライター()メソッドは、V =真書き込まれると仮定し、スレッドBが読み出した場合、スレッドBは、リーダー()メソッドを実行し、揮発性に従って、スレッドBは、メモリから変数vを読み出します変数vは、この時点での変数xの値がどのくらいある、本当ですか??
あなたが1.5未満を使用している場合は、xの値は42がある可能性があります。サンプルプログラムは、直感は、xの値は、実際には、42であるが、xの値が数およびJDKの特定のバージョンで提供し、JDKのバージョン、それはまた、0であってもよいです。バージョン1.5およびJDK 1.5以上の場合は、値は42 Xあります。
これを参照してください、誰かがの質問をするのだろうか?これはなぜでしょうか?実際には、答えは原則の導入が事前発生のJavaメモリモデルのJDK1.5バージョンです。
事前発生原則
次に、我々は原理が事前発生のJavaメモリモデル説明するためにケースのプログラムを組み合わせています。
[A]程度の手続き規則の原理
1つのスレッドで、コードの順序は、前の操作では、事前発生後続の操作。
[例]例えば、プログラム中のx = 42、真= Vに先立って実行されます。このルールは、より多くのシングルスレッドの考え方に沿ったものである:同じスレッド内の変数を変更するプログラムの前面には、次の動作に見えなければなりません。
[二]原則volatile変数のルール
volatile変数への書き込み操作は、事前発生この変数のその後の読み出し動作。
すなわち、最初の読み出し動作後、揮発性変数を使用して、書き込み動作は、この変数の面で発生しました。私たちは、この理解に焦点を当てる必要があります。
[三]転送ルールの原則
AがBの前に、発生し、Bが発生し、前にC場合、Aは、事前発生C.
私たちは、[]、[]と[第2の原理原則3] [サンプルアプリケーションを見てこのケースでは、我々は次のような結論を引き出すことができるの原理を組み合わせました:
(1)X = 42が発生する前に、変数の書き込み順序の[A]手続規則の原則に沿って、=真V。
変数の書き込み(2)V = trueが事前発生[2]の揮発性変数ルールの原理に沿って、変数v =真を読み取ります。
X = 42が事前発生=真の変数vを読んで:次に[3]転送ルールの原則に従って、私たちは、と結論付けることができます。
スレッドBが真V =に読み出す場合には、その後、スレッドBのX = 42スレッドAセットが表示されています。換言すれば、この時点で、スレッドBは、x = 42へのアクセスを得ることができます。
実際には、ツールのJavaのバージョン1.5には、視認性を実現するために揮発性java.util.concurrentの同時セマンティクスに依存することです。
[四]原則ルールをロック
ロックの解除動作が起こる前に、ロックオン、その後のロック操作。
例えば、同期コードブロックに入る前に、次のコードは、コードのブロックが終了した後、それが自動的にロックを解除し、自動的にロックされます。
実施例[2]
public class Test{
private int x = 0;
public void initX{
synchronized(this){ //自动加锁
if(this.x < 10){
this.x = 10;
}
} //自动释放锁
}
}
我々は、この手順を理解することができる:変数xは、実行スレッドA同期コードブロック変数xが値10に修正されるの完了後、10であり、そして同期ロックを解除すると仮定する。スレッドが同期コードブロックBに入ると、スレッドBへのアクセスは、変数x 10であるX変数Aにスレッド書き込みを得ることができます。
原理V] [スレッドの開始規則
スレッドAがスレッドBの呼び出しがスレッドBを開始する()メソッドを起動すると、開始()操作が発生し、前にスレッドBの任意のアクション
また、このスレッドがルールを開始し理解することができます:Aは、スレッドBが動作スレッドBを開始する前にスレッドを見ることができ、スレッドBの後にスレッドを開始します
次のコードで見てみましょう。
[例三]
//在线程A中初始化线程B
Thread threadB = new Thread(()->{
//此处的变量x的值是多少呢?答案是100
});
//线程A在启动线程B之前将共享变量x的值修改为100
x = 100;
//启动线程B
threadB.start();
コードは、原則Vスレッドを[開始]の規則に従って、スレッドAで実行されるコードフラグメントである、スレッドAは、スレッドBの後に開始し、スレッドBはスレッドBを開始する前に動作を確認することができ、スレッド、アクセス中のスレッドB変数の値×100。
原則VI] [糸端ルール
スレッドBのスレッドを待つは、スレッドBが終了すると、スレッドAは、スレッドBにアクセスすることができる(スレッドBを呼び出すスレッドAは、join()メソッドの戻り)、(()メソッドでスレッドAにスレッドBに参加する呼び出し)が完了し操作変数を共有しました。
たとえば、以下の操作は、スレッドAに実行されます
実施例[4]
Thread threadB = new Thread(()-{
//在线程B中,将共享变量x的值修改为100
x = 100;
});
//在线程A中启动线程B
threadB.start();
//在线程A中等待线程B执行完成
threadB.join();
//此处访问共享变量x的值为100
七の原則] [スレッドの割り込みのルール
スレッド割り込み()メソッドは、事前発生ねじコードで中断されているイベントが発生する割り込み検出されたと呼ばれています。
たとえば、次のプログラムコード。スレッドAの中断前にスレッドBの値は、修飾された共有変数xは糸切れが変数x 100の値にイベントB、アクセスが検出された場合、100です。
例[5]
//在线程A中将x变量的值初始化为0
private int x = 0;
public void execute(){
//在线程A中初始化线程B
Thread threadB = new Thread(()->{
//线程B检测自己是否被中断
if (Thread.currentThread().isInterrupted()){
//如果线程B被中断,则此时X的值为100
System.out.println(x);
}
});
//在线程A中启动线程B
threadB.start();
//在线程A中将共享变量X的值修改为100
x = 100;
//在线程A中中断线程B
threadB.interrupt();
}
[8]原則オブジェクト確定原理
スタートへのオブジェクトの完全な初期化が起こる前に、そのファイナライズ()メソッド。
たとえば、次のプログラムコード。
[例] 6
public class TestThread {
public TestThread(){
System.out.println("构造方法");
}
@Override
protected void finalize() throws Throwable {
System.out.println("对象销毁");
}
public static void main(String[] args){
new TestThread();
System.gc();
}
}
実行結果を以下に示します。
构造方法
对象销毁
finalキーワードのほかに
それは変更されません変数のキーワードの最終修正を使用してください。しかし、1.5のJavaより前のバージョンでは、最終的に修正される変数は、Java 1.5バージョンの後、最終的にキーワードに修正される変数を並べ替えるためのJavaメモリモデルは特定の制約た、エラーが表示されます。限り、我々は正しいコンストラクタを提供することが可能であるとして問題になることはありません。
たとえば、次のプログラムコードは、このコンストラクタグローバル変数global.obj、に割り当てられるオブジェクトの初期化が完了していないで、そのオブジェクトが初期化を完了していない、オブジェクトの初期化は、この時点で完了していない、と言って重要なことを三回!!global.obj X値によって読み取らスレッドがゼロであってもよいです。
[例] 7
final x = 0;
public FinalFieldExample() { // bad!
x = 3;
y = 4;
// bad construction - allowing this to escape
global.obj = this;
}
上記の例から:http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#finalWrong
実装の基礎となるJavaのメモリモデル
メインメモリは、バリア(メモリバリア)の並べ替え、基盤となるアーキテクチャに応じてタイムコンパイラ、これらの特定のCPU命令を置き換えるためのメモリバリアによって禁止されています。それは、最適化を行うことができますREORDERコンパイラの場合、メモリバリアが制限されます。そして、プロセッサ、メモリバリアは、キャッシュのリフレッシュ動作につながります。例えば、揮発性のために、コンパイラは、揮発性のフィールドの前と後のそれぞれの読み取りおよび書き込み操作のためのいくつかのメモリバリアを挿入します。