volatileキーワードの役割を深く理解する(三)
4156 ワード
(三)Javaのメモリモデル
メモリモデルおよび同時プログラミングで発生する可能性のあるいくつかの問題について説明した.次にJavaメモリモデルを見て、Javaメモリモデルが私たちにどのような保証を提供し、javaでどのような方法とメカニズムを提供して、マルチスレッドプログラミングを行うときにプログラムの実行の正確性を保証できるかを研究します.
Java仮想マシン仕様では、Javaメモリモデル(Java Memory Model,JMM)を定義して、各ハードウェアプラットフォームとオペレーティングシステムのメモリアクセスの違いを遮断し、Javaプログラムがさまざまなプラットフォームで一貫したメモリアクセス効果を達成できるようにしようとしています.ではJavaメモリモデルは、プログラム内の変数のアクセスルールを定義し、プログラムの実行順序を大きく定義しています.Javaメモリモデルは、より良い実行性能を得るために、実行エンジンがプロセッサのレジスタまたはキャッシュを使用して命令実行速度を向上させることを制限するものではなく、コンパイラが命令を並べ替えることを制限するものでもないことに注意してください.すなわちjavaメモリモデルでもキャッシュ整合性の問題や命令の並べ替えの問題がある.
Javaメモリモデルでは、すべての変数がプライマリメモリ(前述の物理メモリと同様)に存在し、各スレッドには独自のワークメモリ(前のキャッシュと同様)があることを規定しています.スレッドによる変数のすべての操作は、プライマリ・メモリを直接操作することなく、ワークメモリで行う必要があります.また、各スレッドは他のスレッドのワークメモリにアクセスできません.
ok 栗を挙げると以下のJavaコードがあります
実行スレッドは、変数iが存在するキャッシュ行を自分の作業スレッドで付与してから、プライマリ・メモリに書き込む必要があります.プライマリ・メモリに直接数値10を書き込むのではありません.
では、Java言語自体は原子性、可視性、秩序性にどのような保証を提供しているのでしょうか.
1.原子性
Javaでは、基本データ型の変数の読み出しおよび付与操作は原子的な操作であり、これらの操作は中断されてはいけないか、実行されているか、実行されていないかである.
OK栗をもう一つあげます
では、上記の文は原子的な操作ではないかと分析してみましょう.どう見ても、上の4つの文の操作はすべて原子的な操作だと言う友达もいるかもしれません.実は文1だけが原子的操作で、他の3つの文は原子的操作ではありません.
文1は、数値10をxに直接割り当てる、すなわち、スレッドがこの文を実行すると、数値10がワークメモリに直接書き込まれる.
文2は実際には2つの操作を含み,まずxの値を読み出し,xの値をワークメモリに書き込む.xの値を読み出し,xの値をワークメモリに書き込む2つの操作はいずれも原子的な操作であるが,合わせて原子的な操作ではない.
同様に、x++とx=x+1は、xの値を読み出し、1を加算して新しい値を書き込む3つの操作を含む.
したがって,上記4つの文は文1の操作のみが原子性を備えている.
すなわち、単純な読み取り、割り当て(変数に数値を割り当てる必要があり、変数間の相互割り当ては原子操作ではない)のみが原子操作である.(ただし、ここで注意すべき点は、32ビットプラットフォームでは64ビットデータの読み出しと付与は2つの操作で行う必要があり、その原子性は保証されていないことです.しかし、最新のJDKでは、JVMは64ビットデータの読み出しと付与も原子性操作であることが保証されているようです)
以上から,Javaメモリモデルは基本読み出しと付与値が原子的操作であることのみを保証しており,より広い範囲の操作の原子性を実現するにはsynchronizedとLockによって実現できる.synchronizedとLockは、いずれかの時点で1つのスレッドだけがコードブロックを実行することを保証することができるため、自然に原子性の問題が存在せず、原子性が保証される.
2.可視性
可視性の場合、Javaはvolatileキーワードを提供して可視性を保証します.共有変数がvolatileによって修飾されると、変更された値がすぐにプライマリ・メモリに更新され、他のスレッドが読み取りを必要とすると、メモリから新しい値が読み出されます.一方、通常の共有変数は可視性を保証できません.通常の共有変数が変更された後、いつホストメモリに書き込まれるかは不確定であり、他のスレッドが読み取られると、メモリに元の古い値が残る可能性があるため、可視性を保証できません.
また、synchronizedとLockによっても可視性が保証され、synchronizedとLockは、同じ時点で1つのスレッドだけがロックを取得して同期コードを実行することを保証し、ロックを解除する前に変数の変更をプライマリ・メモリにリフレッシュすることができる.したがって、可視性を保証できます.
3.秩序性
Javaメモリモデルでは、コンパイラとプロセッサが命令を再ソートできますが、再ソートプロセスは単一スレッドプログラムの実行には影響しませんが、マルチスレッド同時実行の正確性に影響します.
Javaではvolatileキーワードで一定の「秩序性」を保証できます(具体的な原理は次の節で説明します).またsynchronizedとLockによって秩序性を保証することができ、synchronizedとLockは各時刻に1つのスレッドが同期コードを実行することを保証し、スレッドを順次同期コードを実行させることに相当し、自然に秩序性を保証する.また、Javaメモリモデルは、happens-beforeの原則とも呼ばれる、あらゆる手段で保証される秩序性を必要としない先天的な「秩序性」を備えています.2つの操作の実行順序がhappens-beforeの原則から導き出されない場合、それらはそれらの秩序性を保証することができず、仮想マシンは任意にそれらを再ソートすることができる.
次にhappens-before原則(先行発生原則)を具体的に紹介する.プログラム順序規則:1つのスレッド内で、コード順序に従って、前に書く操作は、後に書く操作 で先に発生する.ロック規則:1つのunLock操作は、同じロック額lock操作 の後に先行する. volatile変数規則:1つの変数に対する書き込み操作は、この変数に対する読み取り操作 の後に先行する.伝達規則:操作Aが操作Bに先行する、操作Bが操作Cに先行する場合、操作Aが操作C に先行して発生することが分かる.スレッド起動規則:Threadオブジェクトのstart()メソッドは、このスレッドの各動作 において先行する.スレッド割込みルール:スレッドinterrupt()メソッドの呼び出しは、割込みスレッドのコードによって割込みイベントが検出された発生 に先行する.スレッド終端規則:スレッド内のすべての操作がスレッドの終端検出に先行して発生し、Thread.join()メソッドによって終了し、Thread.isAlive()の戻り値手段によってスレッドが の実行を終了したことを検出することができる.オブジェクト終端規則:オブジェクトの初期化完了が彼のfinalize()メソッドの開始 に先行して発生する
この8つの原則は「Java仮想マシンを深く理解する」から抜粋されている.
最初の4つのルールについて説明します.
プログラム順序規則にとって、私の理解は、プログラムコードの実行が単一のスレッドで秩序正しく見えることです.なお、このルールでは、仮想マシンがプログラムコードを命令的に並べ替える可能性があるため、「前に書く操作は、後に書く操作より先に発生する」と記載されているが、これはプログラムが実行されるように見える順序でコード順に実行されるべきである.並べ替えは行われますが、最終的に実行される結果はプログラム順序で実行される結果と一致し、データ依存性のない命令のみを並べ替えます.したがって、単一スレッドでは、プログラム実行が秩序正しく実行されているように見えますが、この点は理解してください.実際、このルールは、プログラムが単一スレッドで実行される結果の正確性を保証するために使用されますが、プログラムがマルチスレッドで実行される正確性は保証されません.
第2のルールも理解しやすい.すなわち、単一スレッドでもマルチスレッドでも、同じロックがロックされた状態であれば、ロックを解除してからロック操作を継続しなければならない.
第三条ルールは比較的重要なルールであり、後述する内容でもある.直感的に説明すると、1つのスレッドが先に変数を書き、1つのスレッドが読み取りを行うと、書き込み操作は必ずリード操作で先に発生する.
第4のルールは実際にhappens-beforeの原則が伝達性を備えていることを体現している.
参考資料:http://www.cnblogs.com/dolphin0520/
メモリモデルおよび同時プログラミングで発生する可能性のあるいくつかの問題について説明した.次にJavaメモリモデルを見て、Javaメモリモデルが私たちにどのような保証を提供し、javaでどのような方法とメカニズムを提供して、マルチスレッドプログラミングを行うときにプログラムの実行の正確性を保証できるかを研究します.
Java仮想マシン仕様では、Javaメモリモデル(Java Memory Model,JMM)を定義して、各ハードウェアプラットフォームとオペレーティングシステムのメモリアクセスの違いを遮断し、Javaプログラムがさまざまなプラットフォームで一貫したメモリアクセス効果を達成できるようにしようとしています.ではJavaメモリモデルは、プログラム内の変数のアクセスルールを定義し、プログラムの実行順序を大きく定義しています.Javaメモリモデルは、より良い実行性能を得るために、実行エンジンがプロセッサのレジスタまたはキャッシュを使用して命令実行速度を向上させることを制限するものではなく、コンパイラが命令を並べ替えることを制限するものでもないことに注意してください.すなわちjavaメモリモデルでもキャッシュ整合性の問題や命令の並べ替えの問題がある.
Javaメモリモデルでは、すべての変数がプライマリメモリ(前述の物理メモリと同様)に存在し、各スレッドには独自のワークメモリ(前のキャッシュと同様)があることを規定しています.スレッドによる変数のすべての操作は、プライマリ・メモリを直接操作することなく、ワークメモリで行う必要があります.また、各スレッドは他のスレッドのワークメモリにアクセスできません.
ok 栗を挙げると以下のJavaコードがあります
i = 10;
実行スレッドは、変数iが存在するキャッシュ行を自分の作業スレッドで付与してから、プライマリ・メモリに書き込む必要があります.プライマリ・メモリに直接数値10を書き込むのではありません.
では、Java言語自体は原子性、可視性、秩序性にどのような保証を提供しているのでしょうか.
1.原子性
Javaでは、基本データ型の変数の読み出しおよび付与操作は原子的な操作であり、これらの操作は中断されてはいけないか、実行されているか、実行されていないかである.
OK栗をもう一つあげます
x = 10; // 1
y = x; // 2
x++; // 3
x = x + 1; // 4
では、上記の文は原子的な操作ではないかと分析してみましょう.どう見ても、上の4つの文の操作はすべて原子的な操作だと言う友达もいるかもしれません.実は文1だけが原子的操作で、他の3つの文は原子的操作ではありません.
文1は、数値10をxに直接割り当てる、すなわち、スレッドがこの文を実行すると、数値10がワークメモリに直接書き込まれる.
文2は実際には2つの操作を含み,まずxの値を読み出し,xの値をワークメモリに書き込む.xの値を読み出し,xの値をワークメモリに書き込む2つの操作はいずれも原子的な操作であるが,合わせて原子的な操作ではない.
同様に、x++とx=x+1は、xの値を読み出し、1を加算して新しい値を書き込む3つの操作を含む.
したがって,上記4つの文は文1の操作のみが原子性を備えている.
すなわち、単純な読み取り、割り当て(変数に数値を割り当てる必要があり、変数間の相互割り当ては原子操作ではない)のみが原子操作である.(ただし、ここで注意すべき点は、32ビットプラットフォームでは64ビットデータの読み出しと付与は2つの操作で行う必要があり、その原子性は保証されていないことです.しかし、最新のJDKでは、JVMは64ビットデータの読み出しと付与も原子性操作であることが保証されているようです)
以上から,Javaメモリモデルは基本読み出しと付与値が原子的操作であることのみを保証しており,より広い範囲の操作の原子性を実現するにはsynchronizedとLockによって実現できる.synchronizedとLockは、いずれかの時点で1つのスレッドだけがコードブロックを実行することを保証することができるため、自然に原子性の問題が存在せず、原子性が保証される.
2.可視性
可視性の場合、Javaはvolatileキーワードを提供して可視性を保証します.共有変数がvolatileによって修飾されると、変更された値がすぐにプライマリ・メモリに更新され、他のスレッドが読み取りを必要とすると、メモリから新しい値が読み出されます.一方、通常の共有変数は可視性を保証できません.通常の共有変数が変更された後、いつホストメモリに書き込まれるかは不確定であり、他のスレッドが読み取られると、メモリに元の古い値が残る可能性があるため、可視性を保証できません.
また、synchronizedとLockによっても可視性が保証され、synchronizedとLockは、同じ時点で1つのスレッドだけがロックを取得して同期コードを実行することを保証し、ロックを解除する前に変数の変更をプライマリ・メモリにリフレッシュすることができる.したがって、可視性を保証できます.
3.秩序性
Javaメモリモデルでは、コンパイラとプロセッサが命令を再ソートできますが、再ソートプロセスは単一スレッドプログラムの実行には影響しませんが、マルチスレッド同時実行の正確性に影響します.
Javaではvolatileキーワードで一定の「秩序性」を保証できます(具体的な原理は次の節で説明します).またsynchronizedとLockによって秩序性を保証することができ、synchronizedとLockは各時刻に1つのスレッドが同期コードを実行することを保証し、スレッドを順次同期コードを実行させることに相当し、自然に秩序性を保証する.また、Javaメモリモデルは、happens-beforeの原則とも呼ばれる、あらゆる手段で保証される秩序性を必要としない先天的な「秩序性」を備えています.2つの操作の実行順序がhappens-beforeの原則から導き出されない場合、それらはそれらの秩序性を保証することができず、仮想マシンは任意にそれらを再ソートすることができる.
次にhappens-before原則(先行発生原則)を具体的に紹介する.
この8つの原則は「Java仮想マシンを深く理解する」から抜粋されている.
最初の4つのルールについて説明します.
プログラム順序規則にとって、私の理解は、プログラムコードの実行が単一のスレッドで秩序正しく見えることです.なお、このルールでは、仮想マシンがプログラムコードを命令的に並べ替える可能性があるため、「前に書く操作は、後に書く操作より先に発生する」と記載されているが、これはプログラムが実行されるように見える順序でコード順に実行されるべきである.並べ替えは行われますが、最終的に実行される結果はプログラム順序で実行される結果と一致し、データ依存性のない命令のみを並べ替えます.したがって、単一スレッドでは、プログラム実行が秩序正しく実行されているように見えますが、この点は理解してください.実際、このルールは、プログラムが単一スレッドで実行される結果の正確性を保証するために使用されますが、プログラムがマルチスレッドで実行される正確性は保証されません.
第2のルールも理解しやすい.すなわち、単一スレッドでもマルチスレッドでも、同じロックがロックされた状態であれば、ロックを解除してからロック操作を継続しなければならない.
第三条ルールは比較的重要なルールであり、後述する内容でもある.直感的に説明すると、1つのスレッドが先に変数を書き、1つのスレッドが読み取りを行うと、書き込み操作は必ずリード操作で先に発生する.
第4のルールは実際にhappens-beforeの原則が伝達性を備えていることを体現している.
参考資料:http://www.cnblogs.com/dolphin0520/