SlideShare une entreprise Scribd logo
1  sur  21
Java 並行処理プログラミング
第16章

滝波 景
2

はじめに
• あるスレッドが、変数aVariableに値を代入
aVariable = 3;

• 通常なら他のスレッドは問題なく aVariable=3 として値が見れる。
※ただし、正しく同期化されている場合のみ。

• 逆に同期化されていないと次のような問題が発生
3

メモリやプロセッサの問題
• コンパイラがソースコードに書かれている常識的な順序でない順序で命令を作り
だす。
• 変数をメモリではなくプロセッサのレジスタに保存する。
• プロセッサが複数の命令を平行に実行したり、コンパイラが作ったコードとは違
う順序で命令を実行する。
• キャッシュの介在によって、変数への書き込みが主記憶にコミットされる順序が
変わる。
• プロセッサローカルなキャッシュに保存された値が他のプロセッサから見えない
ことがある。
4

結局なぜJMMが必要?
• JMMはそういった変数へのライトがほかのスレッドから可視になるタイミング
に関する問題を、JVMに対して最低限の保証義務をしてくれている。

• 結果
• あらゆるメモリ操作の結果がやる前から予測可能になる
• プログラムの開発が容易になる
5

メモリモデルの課題
• 共有メモリを使うマルチプロセッサのアーキテクチャでは、各プロセッサが自分
のキャッシュを持つ。
• キャッシュの一貫性もプロセッサのアーキテクチャによってさまざま。
• 各プロセッサは他のプロセッサが何をしているかを知るのは高コストでむしろ、
知らなくていい時の方が多い。なので実際はプロセッサはキャッシュの一貫性を
ゆるめている。
• Javaはこういったアーキテクチャ毎のメモリモデルの違いを開発者に意識させな
くてもいいようにするためにJMMを提供している。
• また、JVMはJMMとプラットフォームのメモリモデルの違いを吸収するために、プ
ログラムがメモリシステムに期待していい保証内容と、データを共有するときに
必要なメモリ保証を取得するための命令(メモリバリヤ)を随所に挿入する。
6

注意点
• プログラマはプログラムが書かれている順序がどんなプロセッサ上でも順番通り
に動くことを想定している。
これを逐次的一貫性という。
• ただ、実際はマルチプロセッサは逐次的一貫性を提供しないし、JMMも提供しな
い。
• なので、プログラマは複数スレッドがデータを共有しているときは、おかしなこ
とが起きることを意識しないとダメ。
• 逆に言えば、共有データがある場所は正しく同期化すればいいだけ。
7

順序変え
• JMMは「一連の活動を構成する複数の操作の順序の見え方がスレッド毎に違っ
てもよい」としています。

• 結果、同期化をしないときの実行順序を判断するのが一層難しくなっている。

• ある操作が不可解に遅れたり、へんな順序で実行されるように見えたりする現
象をひっくるめて、順序変えと呼ぶ。
8

• 正しく同期しないと、結果を推測するのは難しいという簡単な並行プログラム
public class PossibleReordering {
static int x = 0;
static int y = 0;
static int a = 0;
static int b = 0;
public static void main(String rags[]) throws InterruptedException {
Thread one = new Thread(new Runnable() {
@Override
public void run() {
a = 1;
x = b;
}
});
Thread other = new Thread(new Runnable() {
@Override
public void run() {
b = 1;
y = b;
}
});
one.start();
other.start();
one.join();
other.join();
System.out.println("(" + x + "," + y + ")");
}
9

結果
・結果として(1,0)、(1,1)、(0,1)が出力されるのは簡単に想像できるが
(0,0)も出力することもある。

なんで??
・スレッドが互いにデータフローの依存性がないので、書かれている通りの順序
で実行されたとしても、キャッシュが主記憶にフラッシュされるタイミングによ
ってはBから見てAの2つの代入が逆の順序で実行されることがあるから。
(0,0)といった順序変えになる場合のスレッドの状態
10

JMMのアクション定義
• JMMは以下のアクションの仕様を定義しています。
・変数のread/write
・モニタのロックとアンロック
・スレッドのスタートとジョイン
・またJMMは「事前発生(happens before)」という半順序を定義しています。

• ↑↑なんで?
Ans.
• アクションBを実行しているスレッドから、別のスレッドのアクションAの結果
が見えることを保証するにはAとBの間に事前発生の関係が必要。
じゃないとJVMが勝手に順序を入れ替えたりしてしまうから。
つまりデータ競合がおきる。
11

同期化の相乗り
• 事前発生順序の強さを利用して、既存の同期化の可視性特性を相乗りすること
ができる。
• 事前発生のルールの「プログラム順序」+「モニタロック or 揮発性変数」を
組み合わせて本来ロックでガードされていない変数へのアクセスを順序化しま
す。
• このテクニックを相乗りと呼ぶのは、何か他の理由で作られた既存の事前発生
順を利用してあるオブジェクトXの可視性を確保するため、わざわざXを公開す
るために、わざわざ事前発生順を作ったりする必要がないために、そう呼ばれ
ている。
• 注)ただし、文の順序に依存しているので、かなり脆弱。
12

同期化の相乗りの例
private final class Sync extends AbstractQueuedSynchronizer {
private static final int RUNNING = 1;
private static final int RAN = 2;
private static final int CANCELLED = 4;
private V result;
private Exception exception;
void innerSet(V v) {
while (true) {
int s = getState();
if (ranOrCancelled(s)) {
return;
}
if (compareAndSetState(s, RAN)) {
break;
}
result = v;
releaseShared(0);
done();
}
V innerGet() throws InterruptedException, ExecutionException {
acquireSharedInterruptibly(0);
if (getState() == CANCELLED) {
throw new CancellationException();
}
if (exception != null) {
throw new ExecutionException(exception);
}
return result;
}
}
13

同期化の相乗り
• 復習
FutureTaskは、tryReleaseSharedの成功呼び出しがつねに、その後の
tryAcquireSharedの呼び出しよりも事前発生するように実装されている。
• 結果
innerSetはreleaseShared(これがtryReleasedSharedを呼び出す)
前にライト、innerGetはacquiredShared(これがtryAcquireSharedを呼び
だす)を呼び出した後にリードするので「プログラム順序」と「揮発性変数」の
組み合わせにより、innerSetによるresultのライトがinnerGetによるresultの
リードよりも確実に発生。
14

公開
• 事前発生の関係がないときに順序変えが起きると、他のスレッドが一部だけ構
築されたオブジェクトを見てしまう。
• すると、そのスレッドはオブジェクトの参照の最新値を見るが、オブジェクト
のステートはその全部または一部が古い値のまま。
他のスレッドが一部だけ構築されたResourceの参照をみてしまう可能性の例

public class UnsafeLazyInitialization {
private static Resource resource;
public static Resource getInstance() {
if (resource == null) {
resource = new Resource();
}
return resource;
①A
}
②B

①B
②A
}

AはResourceを初期化してからresourceが
それを参照するようにset。
しかし、Bから見ると、resourceへのライ
トがResourceのフィールドへのライトの目
に行われている。
15

安全な初期化の慣用句
• 高価なオブジェクトの初期化を、オブジェクトが実際に必要になるまで遅らせ
ると便利な場合がある。
• 多くのスレッドから頻繁に呼び出さなければ、ロックの争奪がほとんどなくて
、実行性能もまぁまぁとのこと。
スレッドスレーフな遅延初期化

public class SafeLazyInitialization {
private static Resource resource;

public synchronized static Resource getInstance() {
if (resource == null) {
resource = new Resource();
}
return resource;
}
}
16

その他の初期化
• 以下のコードはgetInstanceを呼ぶときの、毎回の同期化にかかる費用がなく
なります。
念入りな初期化

public class ResourceFactory {
public static Resource resource = new Resource();
private static Resource getResource() {
return resource;
}

• 以下のコードはResourceがstaticイニシャライザで初期化されるので、明示的
な同期化がいりません。
遅延初期化ホルダークラス

public class ResourceFactory {
private static ResourceHolder {
public static Resource resource = new

Resource();
}
public static Resource getResource() {
return ResouceHolder.resource;
}
17

ダブルチェックロッキング
• 初期のJVMは同期化の実効性能費用が、無争奪の同期ですらかなり高価。
そのため、下記のようなダブルロッキングといったアンチパターンが発生。
ダブルロッキングアンチパターン

public class DoubleCheckedLocking {
private static Resource resource;
public static Resource getInstance() {
if (resource == null) {
synchronized (DoubleCheckedLocking.class) {
if (resource == null) {

resource = new Resource();
}
}
return resource;
}
}
18

ダブルロッキングの問題点
・処理フロー
1.初期化が必要かどうかを同期化なしでチェックする
2.resourceがnullでなければ、それを使う
3.nullなら同じチェックを今度は同期化して行い、再びnullなら共有Resource
を初期化
・結果
確実に唯一のスレッドがResourceオブジェクトを初期化する。
・問題点
すでにコンストラクトされているResourceの参照を取り出す操作は同期化
されません。
つまりスレッドが一部だけ構築されたResourceを見ることになる。
19

初期化の安全性
• 初期化の安全性とは、正しくコンストラクトされた不可変オブジェクトがデ
ータ競合を使った公開でも、同期化なしで複数スレッドから安全に共有され
ること。
また、コンストラクタにセットしたfinalフィールドの正しい値を全てのスレ
ッドが見ることを保証します。
不可変オブジェクトの初期化安全性

public class SafeStates {
private final Map<String, String> states;
public SafeStates() {
states = new HashMap<String, String()>;
states.put("alaska", "AK");
states.put("alabama", "AL");
states.put("wyoming", "WY");
}
public String getAbbreviation(String s) {
return states.get(s);
}
}
20

• また、正しく構築されたオブジェクトのfinalフィールドから到達できる変数(
例えばfinalな配列の成分や、finalフィールドが参照するHashMapの内容)はど
れでも、他のスレッドにとって可視であることが保証されます。
• 注)statesがfinalフィールドでなかったり、コンストラクタ以外のメソッドが
その内容を変えていたら、他のスレッドからそれらのフィールドの不正な値を
見る可能性がでてくる。
つまり、ノンfinalなフィールドから到達できる値や、コンストラクタの後で
変
わることもある値に関しては、同期化を使って可視性を確保する必要がある
。
21

まとめ
• Javaのメモリモデルとは
スレッドが行うメモリ上のアクションの、他のスレッドからの可視性を保証
できる状況を指定する。
• 可視性を指定するには
複数の操作が事前発生と呼ばれる半順序によって確実に順序されること。
また操作単位は個々のメモリ操作や同期操作なので、十分に同期がないと
スレッドが共有データにアクセスしたときに奇妙なことが起きる。
• 対応
2章、3章で説明された高レベルなルール、例えば@GuardByや安全な公開
を使うと、事前発生の低レベルな細部に頼らずにスレッドセーフが確保可能。

Contenu connexe

Tendances

APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。
APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。
APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。Satoshi Mimura
 
systemdでよく使うサブコマンド
systemdでよく使うサブコマンドsystemdでよく使うサブコマンド
systemdでよく使うサブコマンドKazuhiro Nishiyama
 
Kernel vm study_2_xv6_scheduler_part1_revised
Kernel vm study_2_xv6_scheduler_part1_revisedKernel vm study_2_xv6_scheduler_part1_revised
Kernel vm study_2_xv6_scheduler_part1_revisedToshiaki Nozawa
 
東京Node学園#3 Domains & Isolates
東京Node学園#3 Domains & Isolates東京Node学園#3 Domains & Isolates
東京Node学園#3 Domains & Isolateskoichik
 
リナックスに置ける様々なリモートエキスプロイト手法 by スクハー・リー
リナックスに置ける様々なリモートエキスプロイト手法 by スクハー・リーリナックスに置ける様々なリモートエキスプロイト手法 by スクハー・リー
リナックスに置ける様々なリモートエキスプロイト手法 by スクハー・リーCODE BLUE
 
[Basic 9] 並列処理 / 排他制御
[Basic 9] 並列処理 / 排他制御[Basic 9] 並列処理 / 排他制御
[Basic 9] 並列処理 / 排他制御Yuto Takei
 
StackStormではじめる1人Slackのすすめ
StackStormではじめる1人SlackのすすめStackStormではじめる1人Slackのすすめ
StackStormではじめる1人Slackのすすめ光平 八代
 
ラズパイでデバイスドライバを作ってみた。
ラズパイでデバイスドライバを作ってみた。ラズパイでデバイスドライバを作ってみた。
ラズパイでデバイスドライバを作ってみた。Kazuki Onishi
 
位置情報を使ったサービス「スマポ」をPostgreSQLで作ってみた db tech showcase 2013 Tokyo
位置情報を使ったサービス「スマポ」をPostgreSQLで作ってみた db tech showcase 2013 Tokyo位置情報を使ったサービス「スマポ」をPostgreSQLで作ってみた db tech showcase 2013 Tokyo
位置情報を使ったサービス「スマポ」をPostgreSQLで作ってみた db tech showcase 2013 TokyoYoshiyuki Asaba
 
Tomcat環境をインストールする(debian)
Tomcat環境をインストールする(debian)Tomcat環境をインストールする(debian)
Tomcat環境をインストールする(debian)Kimiyuki Yamauchi
 
RとSQLiteによるオミックス解析の促進
RとSQLiteによるオミックス解析の促進RとSQLiteによるオミックス解析の促進
RとSQLiteによるオミックス解析の促進弘毅 露崎
 
HascTool BlockDevelopment
HascTool BlockDevelopmentHascTool BlockDevelopment
HascTool BlockDevelopmentNobuo Kawaguchi
 
.NET Web プログラミングにおける非同期 IO のすべて (Build Insider OFFLINE)
.NET Web プログラミングにおける非同期 IO のすべて (Build Insider OFFLINE).NET Web プログラミングにおける非同期 IO のすべて (Build Insider OFFLINE)
.NET Web プログラミングにおける非同期 IO のすべて (Build Insider OFFLINE)Tusyoshi Matsuzaki
 
イルカさんチームからゾウさんチームに教えたいMySQLレプリケーション
イルカさんチームからゾウさんチームに教えたいMySQLレプリケーションイルカさんチームからゾウさんチームに教えたいMySQLレプリケーション
イルカさんチームからゾウさんチームに教えたいMySQLレプリケーションyoku0825
 
PHPとシグナル、その裏側
PHPとシグナル、その裏側PHPとシグナル、その裏側
PHPとシグナル、その裏側do_aki
 
超簡単! MySQLをWindowsにインストール
超簡単! MySQLをWindowsにインストール超簡単! MySQLをWindowsにインストール
超簡単! MySQLをWindowsにインストールShin Tanigawa
 

Tendances (20)

Iocage
IocageIocage
Iocage
 
APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。
APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。
APASEC 2013 - ROP/JIT を使わずに DEP/ASLR を回避する手法を見てみた。
 
システムコール
システムコールシステムコール
システムコール
 
systemdでよく使うサブコマンド
systemdでよく使うサブコマンドsystemdでよく使うサブコマンド
systemdでよく使うサブコマンド
 
Kernel vm study_2_xv6_scheduler_part1_revised
Kernel vm study_2_xv6_scheduler_part1_revisedKernel vm study_2_xv6_scheduler_part1_revised
Kernel vm study_2_xv6_scheduler_part1_revised
 
東京Node学園#3 Domains & Isolates
東京Node学園#3 Domains & Isolates東京Node学園#3 Domains & Isolates
東京Node学園#3 Domains & Isolates
 
リナックスに置ける様々なリモートエキスプロイト手法 by スクハー・リー
リナックスに置ける様々なリモートエキスプロイト手法 by スクハー・リーリナックスに置ける様々なリモートエキスプロイト手法 by スクハー・リー
リナックスに置ける様々なリモートエキスプロイト手法 by スクハー・リー
 
[Basic 9] 並列処理 / 排他制御
[Basic 9] 並列処理 / 排他制御[Basic 9] 並列処理 / 排他制御
[Basic 9] 並列処理 / 排他制御
 
StackStormではじめる1人Slackのすすめ
StackStormではじめる1人SlackのすすめStackStormではじめる1人Slackのすすめ
StackStormではじめる1人Slackのすすめ
 
Mysql casial01
Mysql casial01Mysql casial01
Mysql casial01
 
ラズパイでデバイスドライバを作ってみた。
ラズパイでデバイスドライバを作ってみた。ラズパイでデバイスドライバを作ってみた。
ラズパイでデバイスドライバを作ってみた。
 
位置情報を使ったサービス「スマポ」をPostgreSQLで作ってみた db tech showcase 2013 Tokyo
位置情報を使ったサービス「スマポ」をPostgreSQLで作ってみた db tech showcase 2013 Tokyo位置情報を使ったサービス「スマポ」をPostgreSQLで作ってみた db tech showcase 2013 Tokyo
位置情報を使ったサービス「スマポ」をPostgreSQLで作ってみた db tech showcase 2013 Tokyo
 
Tomcat環境をインストールする(debian)
Tomcat環境をインストールする(debian)Tomcat環境をインストールする(debian)
Tomcat環境をインストールする(debian)
 
RとSQLiteによるオミックス解析の促進
RとSQLiteによるオミックス解析の促進RとSQLiteによるオミックス解析の促進
RとSQLiteによるオミックス解析の促進
 
HascTool BlockDevelopment
HascTool BlockDevelopmentHascTool BlockDevelopment
HascTool BlockDevelopment
 
Ruby build
Ruby buildRuby build
Ruby build
 
.NET Web プログラミングにおける非同期 IO のすべて (Build Insider OFFLINE)
.NET Web プログラミングにおける非同期 IO のすべて (Build Insider OFFLINE).NET Web プログラミングにおける非同期 IO のすべて (Build Insider OFFLINE)
.NET Web プログラミングにおける非同期 IO のすべて (Build Insider OFFLINE)
 
イルカさんチームからゾウさんチームに教えたいMySQLレプリケーション
イルカさんチームからゾウさんチームに教えたいMySQLレプリケーションイルカさんチームからゾウさんチームに教えたいMySQLレプリケーション
イルカさんチームからゾウさんチームに教えたいMySQLレプリケーション
 
PHPとシグナル、その裏側
PHPとシグナル、その裏側PHPとシグナル、その裏側
PHPとシグナル、その裏側
 
超簡単! MySQLをWindowsにインストール
超簡単! MySQLをWindowsにインストール超簡単! MySQLをWindowsにインストール
超簡単! MySQLをWindowsにインストール
 

En vedette

Angular js活用事例:filydoc
Angular js活用事例:filydocAngular js活用事例:filydoc
Angular js活用事例:filydocKeiichi Kobayashi
 
はてなのサービスの開発環境
はてなのサービスの開発環境はてなのサービスの開発環境
はてなのサービスの開発環境ast_j
 
第37回NDS Java並行処理 今昔物語
第37回NDS Java並行処理 今昔物語第37回NDS Java並行処理 今昔物語
第37回NDS Java並行処理 今昔物語civic Sasaki
 
pythonでオフィス快適化計画
pythonでオフィス快適化計画pythonでオフィス快適化計画
pythonでオフィス快適化計画Kazufumi Ohkawa
 
VagrantからDockerに開発環境を移行した時の話
VagrantからDockerに開発環境を移行した時の話VagrantからDockerに開発環境を移行した時の話
VagrantからDockerに開発環境を移行した時の話Daijiro Abe
 
10分でわかるPythonの開発環境
10分でわかるPythonの開発環境10分でわかるPythonの開発環境
10分でわかるPythonの開発環境Hisao Soyama
 

En vedette (6)

Angular js活用事例:filydoc
Angular js活用事例:filydocAngular js活用事例:filydoc
Angular js活用事例:filydoc
 
はてなのサービスの開発環境
はてなのサービスの開発環境はてなのサービスの開発環境
はてなのサービスの開発環境
 
第37回NDS Java並行処理 今昔物語
第37回NDS Java並行処理 今昔物語第37回NDS Java並行処理 今昔物語
第37回NDS Java並行処理 今昔物語
 
pythonでオフィス快適化計画
pythonでオフィス快適化計画pythonでオフィス快適化計画
pythonでオフィス快適化計画
 
VagrantからDockerに開発環境を移行した時の話
VagrantからDockerに開発環境を移行した時の話VagrantからDockerに開発環境を移行した時の話
VagrantからDockerに開発環境を移行した時の話
 
10分でわかるPythonの開発環境
10分でわかるPythonの開発環境10分でわかるPythonの開発環境
10分でわかるPythonの開発環境
 

Java並行処理プログラミング 第16章ver2