最近、ピコラボでは有志による勉強会、読書会も開かれています。以下、読書会の議事録を紹介します。ご興味ありましたら、「お問い合わせ」フォームで気軽にコメントなど頂けると幸いです(なお、議事録の議論内容等は各参加者個人の意見であり、会社の意見ではない点、ご了承ください)。

読書会情報

  • 書籍
    • 増補改訂版 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 >= mmoney -= m の間に別スレッドが money += m しても問題ないのでは?
  • 回答: -=+= の操作が不可分とは限らない。
  • (続): また、スレッドごとにキャッシュを持っており、別スレッドによる代入がすぐに見えるとも限らない。
  • (続): 仮に withdraw メソッドが synchronized でなかったとする。すると、預金残高 1000 円の状態で二つのスレッドが同時に 1000 円ずつ withdraw した結果、どちらも true を返し、かつ、預金残高が (-1000 円ではなく) 0 円になる可能性もある。
  • 補足: この話題は教科書の付録 B 「Java のメモリモデル」で説明されている。

notifyAll したときの挙動について

  • 質問: notifyAll するとウェイトセットの一つがロックを取得するのか?
  • 回答: 違う。notifyAll した時点では、notifyAll を呼んだスレッドがロックを所有したまま実行を続ける。
  • (続): そのスレッドがロックを解放すると、ブロックしているスレッドの一つがロックを取得できる。
  • (続): wait せずに単にブロックしていたスレッドが他にあれば、そのスレッドがロックを取得することもある。