JAvaマルチスレッドと同時javaスレッドの概要(四)

6992 ワード

データへのアクセスの共有
----------
共有変数
複数のスレッドを1つのプログラムで使用するには、結果を相互に通信したり共有したりする方法が必要です.スレッドに結果を共有させる最も簡単な方法は
共有変数を使用します.また
同期を使用して、あるスレッドから別のスレッドに値が正しく伝播していることを確認し、あるスレッドが関連するデータ項目を更新している間に、別のスレッドが一致しない中間結果を見ないようにします.
同じメモリ領域に存在するすべてのスレッド
前述したように,スレッドとプロセスには多くの共通点があり,異なる点は
スレッドは、メモリを含む同じプロセス内の他のスレッドと同じプロセスコンテキストを共有します.これはとても便利ですが、重大な責任もあります.共有変数(静的またはインスタンスフィールド)にアクセスすれば、スレッドは容易にデータを交換できますが、
スレッドは、互いに相手の変更を干渉しないように、共有変数に制御された方法でアクセスする必要があります.任意のスレッドは、プライマリ・スレッドが変数にアクセスできるように、その役割ドメイン内のすべての変数にアクセスできます.
管理されたアクセスの同期
Java言語では、スレッド間で制御された方法でデータを共有できるようにする2つのキーワードが用意されています.
synchronizedと
volatile.
Synchronizedには2つの重要な意味があります.コードの保護された部分(反発、mutual exclusion、またはmutex)を一度に1つのスレッドで実行できることを保証し、1つのスレッドで変更されたデータが他のスレッドに表示されることを保証します(変更の可視性).
同期がない場合、データは容易に不一致状態になります.たとえば、1つのスレッドが2つの相関値(パーティクルの位置やレートなど)を更新し、別のスレッドが2つの値を読み込んでいる場合、最初のスレッドに1つの値しか書かれておらず、別の値も書かれていない場合に、2番目のスレッドの実行をスケジュールすると、古い値と新しい値が表示されます.
同期により、他のスレッドでは実行されるか、実行されないかのいずれかのコードブロックを定義できます.同期原子実行または反発の面は、他の動作環境における臨界セグメントの概念に類似している.
共有データ変更の可視性の確認
同期により、スレッドに一貫したメモリビューが表示されることを確認できます.プロセッサは、キャッシュを使用してメモリへのアクセスを高速化できます(または、コンパイラはレジスタに値を格納してより高速なアクセスを行うことができます).一部のマルチプロセッサアーキテクチャでは、1つのプロセッサのキャッシュでメモリ位置を変更した場合、ライタのキャッシュがリフレッシュされ、リーダのキャッシュが無効になるまで、他のプロセッサにこの変更を表示させる必要はありません.これは、このようなシステムでは、同じ変数に対して、2つの異なるプロセッサで実行される2つのスレッドに2つの異なる値が表示される可能性があることを示しています.これは怖いように聞こえますが、よく見られます.他のスレッドで使用または変更されたデータにアクセスするときに、いくつかのルールに従う必要があることを示します.
Volatileは同期よりも簡単で、基本変数(整数、ブール変数など)の単一インスタンスへのアクセスを制御するのに適しています.
変数がvolatileとして宣言されると、変数に対する書き込み操作はキャッシュを迂回し、メインメモリに直接書き込まれ、変数の読み取りもキャッシュを迂回し、自主メモリを直接取得します.これは、すべてのスレッドがいつ見てもvolatile変数の値が同じであることを示します.適切な同期がない場合、スレッドは古い変数値を表示したり、他の形式のデータ破損を引き起こす可能性があります.
ロックで保護された原子コードブロック
Volatileは、各スレッドが最新の変数値を確認するのに役立ちますが、複数の変数を更新するフラグメントなど、比較的大きなコードフラグメントを保護する必要がある場合があります.同期はモニタまたはロックの概念を使用して、特定のコードブロックへのアクセスを調整します.
Javaオブジェクトごとに関連するロックがあります.同じ時間にJavaロックを持つスレッドは1つしかありません.スレッドがsynchronizedコードブロックに入ると、ロックが使用可能になるまでスレッドがブロックされ、待機します.このロックが使用可能になると、このロックが取得され、コードブロックが実行されます.保護されたコードブロックを終了するように制御すると、コードブロックの最後に到達したり、synchronizedブロックでキャプチャされていない異常が投げ出されたりすると、ロックが解放されます.これにより、所与のモニタによって保護されたコードブロックは、毎回1つのスレッドのみで実行される.
他のスレッドの観点から、このコードブロックは原子と見なすことができ、すべて実行するか、まったく実行しないかのいずれかである.
単純な同期の例
synchronizedブロックを使用すると、他のスレッドの割り込みや計算の中間結果を心配することなく、関連する更新のセットとして実行できます.次のサンプルコードでは、「1 0」または「0 1」が印刷されます.同期していない場合は、「1 1」(または「0 0」と印刷されます.
public class SyncExample { 
  private static lockObject = new Object();
  private static class Thread1 extends Thread { 
    public void run() { 
      synchronized (lockObject) {
        x = y = 0;
        System.out.println(x);
      }
    }
  }

  private static class Thread2 extends Thread { 
    public void run() { 
      synchronized (lockObject) {
        x = y = 1;
        System.out.println(y);
      }
    }
  }

  public static void main(String[] args) {
    new Thread1().run();
    new Thread2().run();
  }
}
は、このプログラムを正しく動作させるために、両方のスレッドで同期を使用する必要があります.
Javaロック
Javaロックは反発形式を統合しています.ロックを保持できるスレッドは毎回1つしかありません.ロックは、コードブロックまたはメソッド全体を保護するために使用され、コードブロック自体ではなく、ロックのアイデンティティがコードブロックを保護していることを覚えておくことが重要です.1つのロックは、多くのコードブロックまたはメソッドを保護します.
逆に、コードブロックがロックによって保護されているからといって、2つのスレッドが同時にコードブロックを実行できないわけではない.2つのスレッドが同じロックを待っている場合、コードを同時に実行できないことを示します.
次の例では、setLastAccess()のsynchronizedブロックは、各スレッドに異なるthingie値があるため、2つのスレッドが同時に制限されずに実行することができる.したがって、synchronizedコードブロックは、2つの実行中のスレッド内の異なるロックによって保護される.
public class SyncExample {
  public static class Thingie {

    private Date lastAccess;

    public synchronized void setLastAccess(Date date) {
      this.lastAccess = date;
    }
  }

  public static class MyThread extends Thread { 
    private Thingie thingie;

    public MyThread(Thingie thingie) {
      this.thingie = thingie;
    }

    public void run() {
      thingie.setLastAccess(new Date());
    }
  }

  public static void main() { 
    Thingie thingie1 = new Thingie(), 
      thingie2 = new Thingie();

    new MyThread(thingie1).start();
    new MyThread(thingie2).start();
  }
}

同期メソッドsynchronizedブロックを作成する最も簡単な方法は、メソッドをsynchronizedとして宣言することです.これは、メソッドボディに入る前に、呼び出し元がロックを取得する必要があることを示します.
public class Point {
  public synchronized void setXY(int x, int y) {
    this.x = x;
    this.y = y;
  }
}

通常のsynchronizedメソッドでは、このロックはオブジェクトであり、メソッドが呼び出されます.静的synchronizedメソッドの場合、このロックはClassオブジェクトに関連するモニタであり、このオブジェクトにメソッドが宣言されます.setXY()がsynchronizedと宣言されているからといって、2つの異なるスレッドがsetXY()を同時に実行できないわけではなく、異なるPointインスタンスのsetXY()を呼び出すだけで同時に実行できます.1つのPointインスタンスの場合、setXY()またはPointの他のsynchronizedメソッドは、一度に1つのスレッドしか実行できません.
同期ブロック
synchronizedブロックの構文はsynchronizedメソッドよりも少し複雑です.ロックがどのブロックを保護するかを明示的に指定する必要があるからです.Pointの次のバージョンは、前のページに表示されたバージョンと同等です.
public class Point {
  public void setXY(int x, int y) {
    synchronized (this) {
      this.x = x;
      this.y = y;
    }
  }
}

このリファレンスをロックとして使用するのはよくありますが、これは必須ではありません.これは、コードブロックがクラス内のsynchronizedメソッドと同じロックを使用することを示す.同期は複数のスレッドが同時に1つのコードブロックを実行することを防止するので、性能上問題があり、
シングルプロセッサシステムでも.同期は、できるだけ最小の保護が必要なコードブロックで使用することが望ましい.スタックベースのローカル変数へのアクセスは、自分が属するスレッドにのみアクセスできるため、保護される必要はありません.
ほとんどのクラスは同期していません
同期はパフォーマンスの損失が小さいため、java.utilのCollectionクラスのような汎用クラスの多くは、内部で同期を使用しません.これは、同期が付加されていない場合、HashMapのようなクラスを複数のスレッドで使用できないことを示している.共有コレクション内のメソッドにアクセスするたびに同期を使用することで、Collectionクラスをマルチスレッドアプリケーションで使用できます.指定されたセットについては、毎回同じロックで同期する必要があります.通常、コレクションオブジェクト自体をロックとして選択できます.
次のページのサンプルクラスSimpleCacheでは、HashMapを使用してスレッドを安全にキャッシュする方法を示します.しかしながら、通常、適切な同期は、各メソッドを同期することを意味するだけではない.
Collectionsクラスは、List、Map、およびSetインタフェース用の便利なパッケージのセットを提供する.MapはCollections.synchronizedMapでカプセル化できます.このマップへのアクセスが正しく同期されていることを確認します.
クラスのドキュメントがスレッドが安全であることを説明していない場合は、そうではないと仮定する必要があります.
例:単純なスレッドで安全なキャッシュ
次のコードサンプルに示すように、SimpleCache.javaはHashMapを使用してオブジェクト・ローダに簡単なキャッシュを提供します.load()メソッドは、オブジェクトのキーを押してオブジェクトをロードする方法を知っています.オブジェクトが1回読み込まれると、そのオブジェクトはキャッシュに格納され、その後のアクセスは毎回すべて読み込まれるのではなく、キャッシュから取得されます.共有キャッシュへのアクセスはsynchronizedブロックによって保護されます.正しく同期されているため、複数のスレッドは、データ破損のリスクなしにgetObjectメソッドとclearCacheメソッドを同時に呼び出すことができます.
public class SimpleCache {
  private final Map cache = new HashMap();

  public Object load(String objectName) { 
    // load the object somehow
  }

  public void clearCache() { 
    synchronized (cache) { 
      cache.clear();
    }
  }

  public Object getObject(String objectName) {
    synchronized (cache) { 
      Object o = cache.get(objectName);
      if (o == null) {
        o = load(objectName);
        cache.put(objectName, o);
      }
    }

    return o;
  }
}

小結はスレッドが実行するタイミングが不確定であるため、共有データへのスレッドのアクセスを制御するために注意する必要があります.そうでない場合、複数の同時スレッドが互いに干渉し、データが破損したり、他のスレッドが共有データの変更をタイムリーに見ることができない可能性があります.同期を使用して共有変数へのアクセスを保護することで、スレッドがプログラム変数と予想可能に対話することを保証できます.各Javaオブジェクトはロックとして機能し、synchronizedブロックは、所与のロックによって保護されたsynchronizedコードを一度に1つのスレッドだけで実行することを保証することができる.