Javaの同期原語であるvolatile,synchronized,final

3294 ワード

前言
前のブログではjavaメモリモデルの基礎、すなわち未同期の場合、未同期のスレッドの実行順序が分からず、実行効果も必ずしもなく、セキュリティが低いと書かれています.このブログではjavaの3つの同期原語についてお話しします.
volatile
volatile変数自体は、1つのvolatile変数の読み取りに対して、常に任意のスレッドがこの変数の最後の書き込みを見ることができるという特性可視性を有する.原子性:任意の単一volatile変数の読み取り、書き込みには原子性があるが、volatile++という複合動作には原子性がない.次はプログラムを見てみましょう
class VolatileExample{
int a=0;
volatile boolean flag=false;

public void writer(){
a=1;//1
flag=true;//2
}
public void reader(){
if(flag){//3
int i=a*a;//4
}
}
}

スレッドAがwriterメソッドを実行すると仮定すると、スレッドBはreaderメソッドを実行し、happens-beforeルールに従って、このプロセスによって確立されたhappens-before関係は、(1)1 happens-before 2,3 happens-before 4(2)volatileルールに従って、2 happens-before 3(3)happens-beforeの伝達性ルールに従って、1 happens-before 4であるので、実行順序は、1-2-3-4である.解析理由:これは、スレッドAがflag変数を書いた後、ローカルメモリAでスレッドAに更新された2つの共有変数の値がメインメモリにリフレッシュされ、スレッドBが同じvolatile変数を読むと、ローカルの変数が無効になり、メインメモリのvolatile変数が読み込まれるためです.volatileのメモリの意味:1.スレッドAはvolatile変数を書きます.実質的には県城Aがこの変数を読もうとするある県城に(共有変数を修正する)情報を出した.2.スレッドBはvolatile変数を読み、実質的に県城Bは前のスレッドから発行された(この変数を書く前に低共有変数で修正した)情報3.スレッドAはvolatile変数を書き、その後スレッドBはこの変数を読み、この過程は実質的にスレッドAがスレッドBにメッセージを送信している.上記のコードをまとめると、古いメモリモデルでは1と2の間にデータ依存性がないため、1と2は並べ替えることができ、その結果、リードスレッドB4を実行すると,スレッドAが1を実行するときの共有変数の変更が必ずしも見られるとは限らない.volatileを追加すると、volatile変数の通常の変数間の再ソートがvolatileのメモリの意味を破壊する可能性がある限り、この再ソートはコンパイラおよびプロセッサによって禁止されます.
ロックされたメモリの意味
ロックのメモリの意味はvolatileのメモリの意味と同じです.しかし、ロックはvolatileよりも強力であり、volatileは単一のvolatile変数の読み取り、書き込みに原子性を保証するだけであり、ロックの反発実行の特性は、ストリートエリアコード全体の読み取りの実行に原子性を確保することができるため、ロックの機能はより強力である.しかし、volatileは、伸縮性と実行性能に優れています.ロックを理解するには、次のコードを見てください.
class MonitorExample{
int a=0;
public synchronized void writer(){//1
a++;//2
}//3
public synchronized void reader(){//4
int i=1;//5
}//6
}

同様に、スレッドAがwriterメソッドを実行すると仮定し、その後、スレッドBがreaderメソッドを実行し、happens-before関係に従って、(1)プログラム順序規則に従って:1 happens-before 2,2 happens-before 3,4 happens-before 5,5 happens-before 6(2)モニタロック規則に従って:3 happens-before 4(3)happens-beforeの伝達性に基づいて、2 happens-before 5がこの過程で、Aスレッドがロックを解除すると、スレッドBは同じロックを取得する.スレッドAは、ロックが解除される前に表示されるすべての共有変数であり、オンラインスレッドBが同じロックを取得すると、すぐにBスレッドに表示されます.
final
finalの場合、コンパイラとプロセッサは2つの並べ替えの原則を遵守します:1.コンストラクション関数内のfinalドメインへの書き込みは、その後、このコンストラクションオブジェクトの音楽を参照変数にコピーし、この2つの操作の間で並べ替えることはできません.2.finalドメインを含むオブジェクトの参照を初めて読むことと、その後このfinalドメインを初めて読むこととの間で並べ替えることはできません.理解を助けるためにコードを説明します
public class FinalExample{
int i;
final int j;
static FinalExample obj;
public FinalExample(){
i=1;
j=2;
}
public static void writer(){
obj=new FinalExample();
}
public static void reader(){
FinalExample object=obj;
int a=object.i;
int b=object.j;
{
}

スレッドAがwriterメソッドを実行し、スレッドBがreaderメソッドを実行するとすると、スレッドBのリードオブジェクト参照とリードオブジェクトのメンバードメインとの間に並べ替えがないとする(なぜこの仮定が必要なのかすぐに説明する)finalドメインを書く再ソートの原則は、オブジェクト参照が任意のスレッドで見えるまで、オブジェクトのfinalドメインが正しく初期化されているのに対し、通常の変数はこの特性を持たないことを保証する.すなわち、final変数jに対する付与文j=2は、構造方法の外に再ソートされず、通常の変数iの代入文は大皿の構造方法以外に並べ替えられる可能性があり,これによりスレッドBが変数iの値を読み取る際に1の値を正しく読めない可能性がある.前述したように、スレッドBのリードオブジェクト参照とリードオブジェクトのメンバー変数との間に並べ替えがないと仮定し、その理由を説明する.これは、finalのもう1つの並べ替えルールのためです.1つのスレッドでは、最初のオブジェクト参照と最初のオブジェクトに含まれるfinalドメインが参照され、JMMはプロセッサの並べ替えを禁止します.すなわち,スレッドBではfinal変数jを読み出す前にオブジェクトobjectを先に読み出すのを見ているが,iの読み出しは必ずしもそうではない,これは誤った読み出し方式である.
まとめ
以前のブログでも、異なるスレッド間の実行はランダムであり、この3つの同期原語は、実行順序を制御し、スレッドの実行をより透明化するためだと書かれていました.