「増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編」第五章『Producer-Consumer』

第五章は『Producer-Consumer』と題されている.生産者 (Producer) と消費者 (Consumer) 2 種類のスレッド群がやり取りするようなプログラムを実現するためのパターンである.ただし両者の間に仲介者 (Channel) が存在し,そのやり取りの助けをする.

パティシエとお客さんとテーブル

この三者の関係を説明するために非常にわかりやすいサンプルプログラムが掲載されている.

登場人物はまず Producer はケーキを作るパティシエ (MakerThread).次に Consumer はケーキを食べるお客さん (EaterThread).そして Channel 役としてテーブル (Table) である.MakerThread はケーキ (ここでは String 型の cake) を作成し table に置く.置かれた cake を EaterThread がとり,それを食べる (cake を表示する) というプログラムである.table には規定のサイズ (ここでは一定の要素数を持った配列で表現している) があり,一定数以上の cake を置くことはできない.MakerThread, EaterThread ともに GuardedSuspension パターンが実装されており,MakerThread は限界以上の cake を table には置くことが出来ず,EaterThread は table に cake がない場合には食べることができない.

正に table を介して MakerThread と EaterThread は各自の処理を並行して行なっている.

仲介者を挟むメリット

Channel 役である table を介さずに MakerThread と EaterThread が直接 cake をやり取りすればいいようにも思える.しかし,Channel 役を挟むことで以下の2つのメリットがある.

処理の余裕

もし EaterThread と MakerThread が直接 cake をやり取りするとする.すると MakerThread は cake を作って EaterThread に渡し,そのまま EaterThread が食べ終わる (処理が終わる) のを待たなくてはならない.なぜなら両者の処理は synchronized で排他処理が行われているから.これは無駄な時間ロスである.

一方 table を介した場合,MakerThread はガード条件が満たされている限り cake を作り続け,その処理は EaterThread の処理に依存することはなく,どんどん cake を作り出すことができる.このように,仲介者が間にいることで Producer と Consumer の処理を独立させ,処理の効率性を上げることができる.

再利用性

synchronized, wait, notifyAll などを使用するのをすべて Channel に任せ,Producer と Consumer はマルチスレッドを意識せずとも処理を行うことができる.その結果,再利用性があがる.

スローガン

スレッドの協調動作では「間に入るもの」を考えよう
スレッドの排他制御では「守るべきもの」を考えよう