Javaのマルチスレッド

ひさしぶりにJavaをやっていてマルチスレッドについて復習したときのメモ。

スレッドとはプロセスに内包されるプログラムの実行単位である。プロセスはひとつ以上のスレッドと、それらのスレッドからアクセス可能なメモリ空間を有する。

スレッドはCPUによって並行、または並列に実行される。

並行と並列

  • 並行 (concurrent)
  • 並列 (parallel)

並行と並列の違いは、CPUが複数のスレッドを同時に実行するかどうかである。
並行は複数のスレッドを同時に実行しない。
並列は複数のスレッドを同時に実行する。
下記は並行と並列を図示したものである。横線がスレッド、白い四角が実行中であることを表す。時間は右方向に進む。

並列に動くかどうかはCPU性能に依存する。

マルチスレッドを用いる利点は、プログラムを実行する際のCPU時間の割り当てをスレッド単位に分割でき、その割り当てをVMにまかせられることである。各スレッドはメモリ空間を共有するため複数のプロセスを強調動作させるプログラミングに比べて、コンテキストスイッチやIPCのコストを減らすことができる。

欠点は、各スレッドがメモリ空間を共有するために競合やデータの不正上書き防止をプログラム側で管理しなければならず、プログラムの抽象化を妨げることである。

各スレッドの実行順序は実行時に決定するため、並行/並列実行時の再現性がなくデバッグを困難にする。

スレッドの実行順序をプログラム側で管理するためにプログラムの並行/並列実行を部分的に禁止する仕組みがある。この仕組みを排他制御という。

マルチプロセスによるプログラミングが、他プロセスからメモリへのアクセスを制限しておきIPCで協調動作させるのに対して、マルチスレッドは制限をプログラマが実装しなければならないため、バグが生まれやすい。

よって、マルチスレッドはプロセスのコンテキストスイッチやIPCのコストを減らすためにプログラムの抽象化を下げるハックといえる。

このプログラムの抽象化を妨げてしまう欠点を補うために、マルチスレッドプログラミングのためのデザインパターンがある。下記の本が参考になる。

2014/12/18 追記

ちなみにnew Thread()するコードを自分で書くとハマるので、Java SDKに標準で備わっているExecutorServiceというクラスを使うのが定石。

また、並列処理が必要なときに毎回new Thread()すると処理がモタつくので、事前にスレッドを起動しておいて必要なときにタスクを渡して実行してもらうスレッドプールという手法が知られているが、これもExecutorServiceで簡単に実現できる。