同期メカニズム

データ一貫性の原則

マルチスレッドアプリケーションを作成さくせいするさい、データ一貫性いっかんせい確保かくほするための2つの原則げんそく

相互排他 (Mutual Exclusion)

クリティカルセクションないのコードは、一度いちどに1つのスレッドのみが実行じっこうできます。つまり、同時どうじに1つのスレッドのみが変数へんすうにアクセスできます。

可視性 (Visibility)

共有きょうゆうデータのあたいがあるスレッドによって変更へんこうされた場合ばあいほかのスレッドがすぐにその変更へんこう確認かくにんできます。

volatile キーワード

Javaでは、かくスレッドが独自どくじのメモリ空間くうかん(ワーキングメモリ)をっています。操作そうさ実行じっこうしたあと、スレッドは使用しようした変数へんすうあたいをメインメモリに更新こうしんし、ほかのスレッドはメインメモリから最新さいしんあたいることができます。

この特性とくせいはプログラムの処理しょり効率こうりつ向上こうじょうさせますが、マルチスレッド環境かんきょうでは変数へんすう可視性かしせい問題もんだいこす可能性かのうせいがあります。

変数へんすうりとみがことなるスレッドで発生はっせいする場合ばあいりスレッドが変数へんすうあたい変更へんこう適時てきじ確認かくにんできない場合ばあいがあり、データの不整合ふせいごうにつながります。このような場合ばあい変数へんすうまえvolatileけると、その変数へんすうかくスレッドのワーキングメモリを使用しようせず、つねにメインメモリからきするようになります。

synchronized キーワード

ことなるスレッドがおなじリソースに同時どうじにアクセスする可能性かのうせいがあります。synchronized目的もくてきは、一度いちどに1つのスレッドのみがおなじリソースを使用しようできるように制御せいぎょすることです。

プログラムで例外れいがい発生はっせいした場合ばあい、スレッドとロックは自動的じどうてき解放かいほうされ、デッドロックは発生はっせいしません。ただし、待機中たいきちゅうのスレッドを中断ちゅうだんさせることはできず、待機中たいきちゅうのスレッドはつづけます。

ロックオブジェクト (Lock Object)

ロックとして使用しようされるオブジェクト:

  1. synchronized method - ロックは現在げんざいのオブジェクトインスタンス
  2. static synchronized method - ロックは現在げんざいのクラスの Class オブジェクト
  3. synchronized(obj) - ロックは括弧かっこない指定していされたオブジェクト

synchronized の制限

  1. 同時どうじに1つのスレッドのみが synchronized ブロックにはいることができます
  2. 待機中たいきちゅうのスレッドが synchronized ブロックを取得しゅとくする順序じゅんじょ保証ほしょうされません

パフォーマンスの考慮

  • ていオーバーヘッド:synchronized ブロックが競合きょうごうしていない場合ばあい(まだロックされていない)
  • こうオーバーヘッド:synchronized ブロックが競合きょうごうしている場合ばあいべつのスレッドによってすでにロックされている)

volatile vs synchronized

特性とくせいvolatilesynchronized
相互排他そうごはいたいいえはい
可視性かしせいはいはい
適用てきよう場面ばめん単純たんじゅん操作そうさ複合ふくごう操作そうさ
パフォーマンスわる
volatilesynchronized でブロックをロックするのにくらべて、より簡潔かんけつなコードをくのに役立やくだちます。volatile使用しようすると、同期どうき責任せきにんを JVM にゆだねるため、自分じぶん処理しょりするよりもエラーが発生はっせいしにくくなります。ただし、volatile宣言せんげんされた変数へんすう頻繁ひんぱん使用しようされる場合ばあい、ブロック全体ぜんたいをロックするよりもパフォーマンスが低下ていかする可能性かのうせいがあります。

CPU キャッシュコヒーレンス

  flowchart LR
    subgraph CPU1[CPU Core 1]
        C1[Cache]
    end
    subgraph CPU2[CPU Core 2]
        C2[Cache]
    end
    MM[Main Memory]

    C1 <--> MM
    C2 <--> MM

可視性問題

ことなる CPU コアのスレッドがおなじメモリ(クラスなど)を共有きょうゆうする場合ばあい適切てきせつ同期どうきメカニズムがないと、あるスレッドによる変数へんすう変更へんこうほかのスレッドに適時てきじ反映はんえいされない可能性かのうせいがあります。

Happens-Before 保証

Happens-Before は、命令めいれいならえが Java の可視性かしせい保証ほしょう破壊はかいすることをふせぐための一連いちれんのルールです。

  • volatile の happens-before 保証ほしょう:volatile 変数へんすうへのみは、そのあとのその変数へんすうりより happens-before
  • synchronized の happens-before 保証ほしょう:ロックの解放かいほうは、そのあとおなじロックの取得しゅとくより happens-before

Race Condition

2つ以上いじょうのスレッドが、不正ふせい結果けっかこす可能性かのうせいのある方法ほうほうおな変数へんすうにアクセスする場合ばあい、レースコンディションが発生はっせいします。

発生条件

  • 2つ以上いじょうのスレッドがおな変数へんすうまたはデータを同時どうじきする
  • スレッドが以下いかのパターンで変数へんすうにアクセスする:
    • Check-then-act確認かくにん実行じっこう
    • Read-modify-write変更へんこうあたい以前いぜんったあたい依存いぞん
  • スレッドの変数へんすうアクセスがアトミックではない

解決策

synchronized ブロックを使用しようして相互排他そうごはいた確保かくほ

CheckThenAct.java
synchronized (lock) {
    if (condition) {
        // 操作を実行
    }
}

参考リソース