最近、ピコラボでは有志による勉強会、読書会も開かれています。以下、読書会の議事録を紹介します。ご興味ありましたら、「お問い合わせ」フォームで気軽にコメントなど頂けると幸いです(なお、議事録の議論内容等は各参加者個人の意見であり、会社の意見ではない点、ご了承ください)。
読書会情報
- 書籍
- 増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編
- 発表対象
- Introduction 1, Introduction 2
質問・議論
for 文の書き方について
発表資料中の以下のコード断片に対して、コードの書き方の議論。
for (int i : IntStream.range(0, 1000)) {
System.out.print(i);
}
- 意見: ストリームの forEach を使う方がスマートではないか?
- 回答: ループ内の処理が副作用を持つときは for 文の方が分かりやすいのでは?と考えて、敢えてこう書いている。
- (続): forEach なら以下のように書ける。
IntStream.range(0, 1000).forEach(i => System.out.print(i));
- 意見: ラムダを使わずにメソッド参照で書ける。
IntStream.range(0, 1000).forEach(System.out::print);
- 質問: その
::
は何か? - 回答: Java でメソッド参照を表すための記法。
System.out.print
とは書けない。 - 質問: C++ の名前空間の区切り文字のようなものか?
- 回答: そうだと思う。
Runnable の実装方法について
- 説明: 教科書は J2SE 5.0 で説明しているため、Runnable の実装クラスを定義している。
- (続): 今の Java なら単純な処理はラムダを使って書けばよい。
スレッドの起動方法について
スレッドの起動方法に関して掘り下げた議論。
- 説明: Thread クラスは、スレッドで実行される処理自身と実行機構が共存しており、よい設計ではない。
- (続): このことは Effective Java にも書かれている。
- (続): 実行機構のみを提供する java.util.concurrent.Executor インタフェースがある。
- 意見: execute メソッドではなく ExecutorService インタフェースの submit メソッドを使うのがよい。
- (続): execute メソッドは値を戻さない (void). submit メソッドは Future を戻す。
- (続): Executors クラスに ExecutorService のファクトリメソッドが定義されている。
synchronized メソッドについて
- 質問: 同一クラスの二つ以上のメソッドを、それぞれ同時に一スレッドのみ実行可能にしたいときは、どう書けばよいか?
- 回答: synchronized メソッドでは書けない。ロック用のオブジェクトを定義して synchronized ブロックを使う。
private Object lock1 = new Object(); private Object lock2 = new Object(); public void method1() { synchronized (lock1) { ... } } public void method2() { synchronized (lock2) { ... } }
- 意見: 上記のコードはロック用のオブジェクトを private にできるメリットもある。
- (続): synchronized メソッドは this をロックに使うので、クラスの外部からもロックを取られる可能性がある。
public void someMethod(Foo foo) { synchronized (foo) { ... } }
- 質問: synchronized なインスタンスメソッドと synchronized なクラスメソッドは同時に実行できるのか?
- 回答: 実行できる。
deposit を synchronized にする必要性について
- 質問: 預金を表す deposit メソッドに synchronized は必要なのか?
- (続):
money >= m
とmoney -= m
の間に別スレッドがmoney += m
しても問題ないのでは? - 回答:
-=
や+=
の操作が不可分とは限らない。 - (続): また、スレッドごとにキャッシュを持っており、別スレッドによる代入がすぐに見えるとも限らない。
- (続): 仮に withdraw メソッドが synchronized でなかったとする。すると、預金残高 1000 円の状態で二つのスレッドが同時に 1000 円ずつ withdraw した結果、どちらも true を返し、かつ、預金残高が (-1000 円ではなく) 0 円になる可能性もある。
- 補足: この話題は教科書の付録 B 「Java のメモリモデル」で説明されている。
notifyAll したときの挙動について
- 質問: notifyAll するとウェイトセットの一つがロックを取得するのか?
- 回答: 違う。notifyAll した時点では、notifyAll を呼んだスレッドがロックを所有したまま実行を続ける。
- (続): そのスレッドがロックを解放すると、ブロックしているスレッドの一つがロックを取得できる。
- (続): wait せずに単にブロックしていたスレッドが他にあれば、そのスレッドがロックを取得することもある。