Java Concerency In Practice第三章読書ノート

4079 ワード

動作の原子性を保証するだけでなく,同期はまた,異なるスレッド間の変数のメモリ視認性を保証することができる。原子性と視認性は共に同期の二つのコア要素を構成している。第三章は主にスレッド間で安全に変数を発表し共有する方法を説明します。
まず本の例を通して、何が視認性ですか?
public class NoVisibility { 
 private static boolean ready; 
 private static int number; 
 private static class ReaderThread extends Thread { 
 public void run() { 
 while (!ready) 
 Thread.yield(); 
 System.out.println(number); 
 } 
 } 
 public static void main(String[] args) { 
 new ReaderThread().start(); 
 number = 42; 
 ready = true; 
 } 
} 
表面では私たちは二つのスレッドを起動し、一つのスレッドはready flagsを無期限でチェックし、もう一つのスレッドはnumberとreadyを変更します。論理的には、スクリーンに42のプリントアウトが期待されますが、実際には視認性の問題で、もう一つのスレッドはreadyの値を永遠に見られないかもしれません。JMMのスレッドは自分の作業メモリを持っています。作業メモリにはメインメモリのコピーが保存されています。JVMは命令を並べ替えて最適化する可能性もあります。シングルスレッドでは結果が期待される論理結果(as-if-serial)が保証されますが、その命令の実行過程は必ずしも声明の順序ではありません。上の方はreadyを先に置いていた可能性があります。結果としてゼロが印刷されました。マルチスレッドで同期やvolatile声明がないと、命令の実行順序に期待できない。
視認性の問題は他のスレッドで得られたデータはすでに期限が切れているかもしれませんが、他のスレッドは何も知らないという問題を引き起こします。したがって、Javaは、視認性を保証するためにvolatileキーワードを提供しています。一つの変数をvolatileとして宣言すると、jvmは書き込み時にスレッドワークメモリの最新値をコピーしてメインメモリの保証値に戻します。また、使うたびにメインメモリの値を更新し、volatile変数に関するすべてのステートメントは並べ替えを禁止します。volatileは視認性を保証できますが、操作の原子性は保証できません。
総じて言えば。volatileは、より軽量化されたスレッドセキュリティ手段として、適用範囲は同期よりも制限されている。書き込みの操作は現在の値に依存してはいけません。volatileは原子性を保証できないからです。2.この変数は他の変数の不変性関係に組み入れられていません。つまり彼は独立しています。3.アクセス時にロックは不要です。
上の三つの条件を満たす場合にのみ、volatileが適用されます。
リリースとは、オブジェクト(実質的にはオブジェクトに対する参照を発表すること)を指し、オブジェクトを現在のスコープから除外して使用することができるようにすることです。発表はパッケージ性を破壊する。リリースすべきでないオブジェクトがリリースされるとオーバーフローが発生します。オブジェクトを外部に公開する最も簡単な方法は、公開されたstatic変数を使用してオブジェクトを保存することである。
public static Set knownSecrets; 
public void initialize() { 
 knownSecrets = new HashSet(); 
} 
公有の静的変数を介して、私たちが新しく作ったHashSetを指し、他のスレッドは新しいHashSetの参照を得た。
対象を発表する場合、他のオブジェクトを間接的に投稿する場合があります。例えば私はhashsetをリリースします。hashsetの中の値も間接的に発表されます。
可変prvateオブジェクトに対して、prvateアクセス権限はこのオブジェクトを現在のクラスにカプセル化し、外部に公開すると他のスレッドについての引用が得られ、prvateを使用した既存の意味に反しているという逸話があります。
class UnsafeStates { 
 private String[] states = new String[] { 
 "AK", "AL" ... 
 }; 
 public String[] getStates() { return states; } 
} 
もう一つの比較的に晦渋な発表の他の対象の例は内部類です。内部類にはouterclassに対する外部アプリケーションが保存されていますので、innnerclassを発表すると間接的に外部類の引用を発表します。思わぬ結果をもたらすかもしれません。
public class ThisEscape { 
 public ThisEscape(EventSource source) { 
 source.registerListener( 
 new EventListener() { 
 public void onEvent(Event e) { 
 doSomething(e); 
 } 
 }); 
 } 
} 
最後にダイナミックなconstruct初期化プロセスです。もし私達がconstructorの構造の中で外部に向けてthis引用を発表するなら、発表された対象は部分構造の可能性が高く、コードを発表するのが最後の行であっても、順序付けが一部構造の可能性があります。だから対象のthis引用はconstructorの構造を完成した後にのみ発表できます。比較的一般的なエラーは、スレッドを構造体内で起動します。スレッドは共有されているので、起動スレッドは完全に構成されていないオブジェクトを見ることができます。だからスレッドでの起動方法は一般的にstartです。
このような状況に対して,著者らの提案は,外部に対象を発表する場合には,静的な工場法を用いてこの動作を完成させることである。
public class SafeListener { 
 
 private final EventListener listener; 
  private SafeListener() { 
 
 listener = new EventListener() { 
 
 public void onEvent(Event e) { 
  doSomething(e); 
  } 
  }; 
  } 
  public static SafeListener newInstance(EventSource source) { 
  SafeListener safe = new SafeListener(); 
 
 source.registerListener(safe.listener); 
  return safe; 
  } 
} 
その他の技術はスレッドの安全を完成することができます。例えば、スレッドが閉じられていて、オブジェクトをスレッドに閉じています。このように、他の同期手段を必要としないのがスレッドの安全です。1.stackはスレッドがプライベートなので、stack内に閉じ込められたオブジェクトはもちろん現在のスレッドのみがアクセスできます。2.ThreadLocal類を使用する。3.スレッド内で可変オブジェクトを発表したら、彼らは必ずスレッドが安全です。次の3つを満たすものは可変対象ではない。オブジェクトの作成後は変更できません。2.ドメインはすべてfinalタイプ3です。オブジェクト構造の過程で、そのthisは引用されていません。
例えば、このような発表はpublicを引用して発表します。見られないため、他のスレッドは最新のオブジェクトを見ることができるとは限りません。静的初期化と動的初期化は異なり、静的初期化はJVMがクラスのロード段階で初期化したもので、JVMは内部の同期機構が間違っていないことを保証することができる。
// Unsafe publication 
public Holder holder; 
public void initialize() { 
 holder = new Holder(42); 
} 
しかし、オブジェクトについては、このオブジェクトの参照を他のスレッドに投稿しても、その状態は必ずしも他のスレッドに対して見られないことを知ることができる。他のスレッドは、プライベートなオブジェクト共有APIを介してのみオブジェクトにアクセスできるからである。したがって、他のスレッドにオブジェクトをリリースしても、API方式と同期してリリースするプロセス(セキュリティリリース)によって、このオブジェクトをスレッドの安全にすることができます。