スレッドセーフティの原子性、順序、可視性について(再現)

並行Javaプログラミングの場合、一般的に次の問題があります。

  1. スレッドの安全性、正確性。

  2. スレッドアクティビティ(デッドロック、ライブロック)

  3. パフォーマンス

中でも、スレッドの安全性の問題が最初に解決される問題であり、スレッドは安全ではなく、操作の結果は期待どおりではなく、基本的な要件さえ満たされていません。

スレッドのセキュリティを確保する問題は、基本的にはスレッドの同期を確保することです。これは、実際にはスレッド間の通信の問題です。オペレーティングシステムでスレッド通信を行う方法はいくつかあります。

  1. セマフォ

  2. 信号

  3. パイプライン

  4. 共有メモリ

  5. メッセージキュー

  6. ソケット

Javaのスレッド通信は、主に共有メモリを使用します。共有メモリの最初の通信方法は、可視性と順序です。通常、アトミック操作が必要なので、これらの3つの問題に焦点を当てます。

1.原子性

原子性とは、操作が不可分であることを意味します。そのパフォーマンスは、シェア変数の特定の操作は不可分であり、継続的に完了する必要があることです。たとえば、++は、共有変数aの操作のために、実際には3つのステップを実行します。

  1. 変数aの値を読み取ります

  2. a + 1の値

  3. 値を変数aに割り当てます。

これらの3つの操作のいずれかの間、aの値が誰かによって改ざんされており、望ましくない結果が表示されます。したがって、これがアトミックであることを確認する必要があります。Javaのロックメカニズムは、原子性の問題を解決します。

2.可視性

可視性は、別のスレッドから見えるかどうかに関係なく、共有変数のスレッドの変更の値です。

なぜそのような問題があるのですか?

Javaスレッドの通信は共有メモリを介して通信されること、そして実行速度を上げるために、スレッドは通常メモリを直接操作するのではなく、キャッシュを操作することを知っています。

Javaスレッドメモリモデル:

                  

実際、スレッドはメインメモリではなく、独自の作業メモリで動作します。スレッドの変数の操作によってメインメモリがフラッシュされず、ワーキングメモリ内の変数の独自のコピーのみが変更される場合、他のスレッドからは見えません。別の変数がメインメモリ内の新しい値を読み取らず、古い値を使用している場合、同じものを非表示としてリストすることもできます。

jvmの場合、メインメモリはすべてのスレッドで共有されるJavaヒープであり、作業メモリの共有変数のコピーはメインメモリからコピーされます。これはスレッドのプライベートローカル変数であり、Javaスタックにあります。

では、ワーキングメモリの変数がメインメモリにフラッシュされるタイミングを知るにはどうすればよいでしょうか。

これには、Javaの前に発生する関係が含まれます。

JMMでは、1つの操作の結果を別の操作に表示する必要がある場合、2つの操作の間に発生前の関係が存在する必要があります。

この人のブログはよく書かれています:http://ifeve.com/easy-happens-before/。

簡単に言えば、発生前の関係が満たされている限り、それらは表示されます。

たとえば、次のとおりです。

スレッドAでi = 1を実行し、スレッドBでj = iを実行します。スレッドAの操作とスレッドBの操作が前に発生する関係を満たす場合、jは1に等しくなければなりません。そうでない場合、jの値は不確定です。

発生前の関係は次のとおりです。

  1. プログラムの順序の規則:スレッド内では、コードの順序に従って、前に書かれた操作が後ろに書かれた操作の前に発生します。

  2. ロック規則:unLock操作は、同じロック量のロック操作の前に発生します。

  3. 揮発性変数の規則:変数への書き込み操作は、この変数への読み取り操作の後に最初に発生します。

  4. 転送ルール:オペレーションAがオペレーションBの前に発生し、オペレーションBがオペレーションCの前に発生する場合、オペレーションAはオペレーションCの前に発生する可能性があります。

  5. スレッド起動ルール:Threadオブジェクトのstart()メソッドは、このスレッドのすべてのアクションで最初に発生します。

  6. スレッド割り込み規則:スレッドinterrupt()メソッドの呼び出しは、割り込みを受けたスレッドのコードが割り込みイベントの発生を検出する前に発生します。

  7. スレッドの終了規則:スレッド内のすべての操作は、スレッドの終了検出で最初に発生します。スレッドが実行を終了したことは、Thread.join()メソッドの終了とThread.isAlive()の戻り値の意味で検出できます。

  8. オブジェクトのファイナライズのルール:オブジェクトの初期化は、finalize()メソッドの最初に行われます。

上記の発生前のルールから、メモリの可視性を実現するには、一般にvolatileキーワードまたはロックメカニズムのみが必要であることは明らかです。

3.注文

順序付けとは、プログラムが実行されるとき、プログラムのコード実行順序がステートメントの順序と同じであることを意味します。

なぜ矛盾があるのですか?

これは再注文によるものです。

Javaメモリモデルでは、コンパイラとプロセッサは命令を並べ替えることができますが、並べ替えプロセスはシングルスレッドプログラムの実行には影響しませんが、マルチスレッドの同時実行の正確さに影響します。

次に例を示します。

スレッドA:

context = loadContext();    
inited = true;    

スレッドB:

while(!inited ){
 sleep
}
doSomethingwithconfig(context);

スレッドAが再注文した場合:

inited = true;    
context = loadContext(); 

次に、スレッドBは構成する初期化されていないコンテンツを取得し、エラーを引き起こします。

この並べ替えはスレッドAのスレッドAの正確さに影響しないため、loadContext()メソッドがブロックされている場合、CPUの使用率を高めるためにこの並べ替えが可能です。

並べ替えを防止する場合は、volatileキーワードを使用する必要があります。volatileキーワードを使用すると、変数の操作が並べ替えられないようにすることができます。

 

著者:a60782885

blog.csdn.net/a60782885/article/details/77803757

おすすめ

転載: www.cnblogs.com/yujian0817/p/12753324.html