増補改訂版
Java 言語で学ぶデザインパターン マルチスレッド編
12. Active Object
内山 雄司 (@y__uti)
2019-08-08 社内勉強会
自己紹介
内山 雄司 (@y__uti)
◦ http://y-uti.hatenablog.jp/ (phpusers-ja)
仕事
◦ 受託開発の会社 (株式会社ピコラボ) でプログラマをしています
興味
◦ プログラミング言語処理系
◦ 機械学習
2019-08-08 社内勉強会 2
発表の流れ
1. Active Object とは?
2. Active Object の実装の説明
3. 議論:Active Object のバリエーション
4. 補講:java.util.concurrent パッケージの利用
2019-08-08 社内勉強会 3
Active Object とは?
能動的な (active) オブジェクト
具体的には
◦ 自分固有のスレッドを持っていて (= 能動的な)
◦ 外部から受け取った非同期メッセージを
◦ 自分固有のスレッドで
◦ 自分の都合がよいタイミングで処理して
◦ 結果を返す
ことができるオブジェクト
2019-08-08 社内勉強会 4
別の解釈
2019-08-08 社内勉強会 5
Active Object パターンでは、シングルスレッドで動作することを前提と
して設計されている Servant 役に皮をかぶせて、マルチスレッドの
Client 役から利用できるようにしている、と見なすこともできますね。
Servant 役に皮をかぶせて = 再利用性
マルチスレッドの Client 役から利用できるようにしている
◦ スレッドセーフ = 安全性
◦ 非同期処理 = 応答性 (パフォーマンス)
(教科書 419 ページより引用)
Active Object の実装
Active Object = Future + Worker Thread
2019-08-08 社内勉強会 6
サンプルプログラム1
クライアント側から見えるもの
2019-08-08 社内勉強会 7
public interface ActiveObject {
// fillchar を count だけ繰り返した文字列を作って返す
public abstract Result<String> makeString(int count, char fillchar);
// 渡された文字列 string を表示する
public abstract void displayString(String string);
}
public abstract class Result<T> {
// 値を取得する
public abstract T getResultValue();
}
(List 12-4 より)
(List 12-12 より)
サンプルプログラム1
ActiveObject の使い方
シングルスレッドならこんな感じ
2019-08-08 社内勉強会 8
// ActiveObject インスタンスを作る ActiveObjectFactory があるとして
ActiveObject activeObject = ActiveObjectFactory.createActiveObject();
Result<String> result = activeObject.makeString(10, "A");
System.out.println(result.getResultValue());
これをマルチスレッドでも使いたい
◦ 非同期処理 - makeString の実行中にクライアントを待たせない
◦ スレッドセーフ - 複数のクライアントから同時に利用可能にする
2019-08-08 社内勉強会 9
(教科書の図を引用)
非同期処理の実現
Future パターンを利用する
Future パターンとは? (復習)
1. Host 役は Client 役からの要求に対して Future を返す
2. 別スレッドで処理を実行して結果を Future に設定する
3. Client は Future から結果を取得できる
2019-08-08 社内勉強会 10
2019-08-08 社内勉強会 11
(教科書の図を引用)
ここをブラックボックスと
考えれば Future と同じ
2019-08-08 社内勉強会 12
Main
request
Host
Data
FutureData
RealDatasetRealData
getContent getContent
(教科書の図を引用)
スレッド安全性の実現
Worker Thread パターンを利用する
Worker Thread パターンとは? (復習)
1. Client 役と Worker 役は Channel を共有している
2. Client は要求を表現する Request を Channel に追加する
3. Worker は Channel を見張っている
4. Channel に Request が来たら取り出して処理を実行する
2019-08-08 社内勉強会 13
2019-08-08 社内勉強会 14
(教科書の図を引用)
2019-08-08 社内勉強会 15
Channel
Request
ClientThread
putRequest
(教科書の図を引用)
2019-08-08 社内勉強会 16
Channel
Request
WorkerThread
takeRequest
execute
(教科書の図を引用)
スレッド安全性の実現
2019-08-08 社内勉強会 17
ワーカースレッドを 1 人にしておくと、ワーカースレッドが行う処理の
範囲はシングルスレッドになりますから、排他制御を省略できる可能性
があります。
(教科書 276 ページより引用)
SchedulerThread = 1 人のワーカースレッド
Servant のメソッドはワーカースレッド上で実行される
◦ 排他制御を省略できる
◦ シングルスレッドを前提とした設計のまま再利用可能
議論
Active Object のバリエーションを考える
2019-08-08 社内勉強会 18
Scheduler の必要性
Servant 役がもともとスレッドセーフなら
⇒ Future パターンを適用するだけで非同期にできる
たとえば教科書の例における Servant
2019-08-08 社内勉強会 19
ActivationQueue への追加
(教科書 412 ページ)
教科書の実装
◦ ActivationQueue が一杯だとクライアントは wait する
バリエーション
◦ Guarded Suspension (第 3 章) ではなく Balking (第 4 章) を利用
2019-08-08 社内勉強会 20
スケジューリング方法
(教科書 412, 420 ページ)
教科書の実装
◦ ActivationQueue の先頭要素を取り出して実行する (FIFO)
バリエーション
◦ ガード条件を確認して実行可能なリクエストを処理する
2019-08-08 社内勉強会 21
参考文献
Lavender, R. Greg; Schmidt, Douglas C. "Active Object"
(https://en.wikipedia.org/wiki/Active_object の References 3 から PDF を取得可能)
試してみよう
displayString を連続して呼んではいけないことにする
2019-08-08 社内勉強会 22
class Servant implements ActiveObject {
private boolean canDisplay = false;
public boolean canDisplay() { return canDisplay; }
public Result<String> makeString(int count, char fillchar) {
canDisplay = true;
...
}
public void displayString(String string) {
if (!canDisplay) { System.out.println("*ERROR*"); return; }
canDisplay = false;
...
}
}
試してみよう
ガード条件を満たすリクエストを処理する
1. MethodRequest クラスに guard メソッドを追加する
2. ActivationQueue の実装を変更する
3. SchedulerThread の run の実装を変更する
2019-08-08 社内勉強会 23
試してみよう
MethodRequest クラスに guard メソッドを追加する
2019-08-08 社内勉強会 24
abstract class MethodRequest<T> {
public abstract boolean guard();
...
}
class MakeStringRequest extends MethodRequest<String> {
public boolean guard() { return true; }
...
}
class DisplayStringRequest extends MethodRequest<Object> {
public boolean guard() { return !servant.IsNeedToMake(); }
...
}
具象クラスでの実装
試してみよう
ActivationQueue の実装を変更する
1. requestQueue をキューからリストに変更
2019-08-08 社内勉強会 25
private final LinkedList<MethodRequest> requestQueue;
2. takeRequest メソッドでガード条件を満たすものを探す
public synchronized MethodRequest takeRequest() {
...
for (MethodRequest request : requestQueue) {
if (request.guard()) {
requestQueue.remove(request);
notifyAll();
return request;
}
}
return null;
}
試してみよう
SchedulerThread の run の実装を変更する
2019-08-08 社内勉強会 26
public void run() {
while (true) {
MethodRequest request = queue.takeRequest();
if (request != null) {
request.execute();
}
try { Thread.sleep(10); } catch (InterruptedException e) {}
}
}
ポイント
◦ ガード条件を満たすリクエストが存在しない場合もある
◦ ガード条件が成立するタイミングは (一般には) わからない
◦ したがって takeRequest 側で wait しては駄目で polling する
デッドロックの可能性
以下の条件でデッドロックに陥る
◦ ActivationQueue が埋まっている
◦ その中にガード条件を満たすリクエストが存在しない
今回の例では DisplayStringRequest でキューが埋まると「死」
◦ キューに空きがないのでリクエストを追加できない
◦ キューに MakeStringRequest が存在しないので要素を取り出せない
2019-08-08 社内勉強会 27
補講
java.util.concurrent パッケージの利用
2019-08-08 社内勉強会 28
サンプルプログラム2
パッケージが提供する機能
◦ 非同期リクエストの表現 MethodRequest
◦ 非同期リクエストの実行管理 SchedulerThread, ActivationQueue
◦ 非同期処理の戻り値の表現 Result, FutureResult, RealResult
(教科書 423 ページの Table 12-3 を参照)
2019-08-08 社内勉強会 29
実装してみよう
java.util.concurrent のクラス群を自分で書いてみよう
2019-08-08 社内勉強会 30
public interface Callable<T> { ... }
public interface ExecutorService { ... }
public class Executors { ... }
public class Future<T> { ... }
class ActivationQueue { ... }
class SingleThreadExecutor implements ExecutorService { ... }
◦ java.util.concurrent を使った場合と同様に動くようにする
実装してみよう
ExecutorService インタフェース
2019-08-08 社内勉強会 31
public interface ExecutorService {
public <T> Future<T> submit(Callable<T> callable);
public void execute(Runnable runnable);
public void shutdown();
}
public class Executors {
public static ExecutorService newSingleThreadExecutor() {
return new SingleThreadExecutor();
}
}
Executors
◦ ExecutorService を実装する SingleThreadExecutor を生成して返す
実装してみよう
戻り値のない処理 (Runnable) の非同期実行
2019-08-08 社内勉強会 32
public class SingleThreadExecutor
extends Thread implements ExecutorService {
// queue は Runnable オブジェクトを格納する ActivationQueue とする
public void execute(Runnable runnable) {
queue.putRequest(runnable);
}
public void run() {
while (true) {
Runnable runnable = queue.takeRequest();
runnable.run();
}
}
}
実装してみよう
戻り値のある処理 (Callable<T>) の非同期実行
2019-08-08 社内勉強会 33
public <T> Future<T> submit(Callable<T> callable) {
Future<T> future = new Future<T>();
Runnable runnable = new Runnable() {
public void run() {
T result = callable.call();
future.set(result);
}
};
execute(runnable);
return future;
}
◦ callable の結果を future に設定する Runnable を作って非同期実行
◦ MakeStringRequest の execute メソッドと同じ仕掛け
◦ この future + runnable が java.util.concurrent.FutureTask<V> に相当
実装してみよう
shutdown による終了処理
2019-08-08 社内勉強会 34
public void shutdown() {
shutdownRequested = true;
}
public void execute(Runnable runnable) throws RuntimeException {
if (shutdownRequested) { throw new RuntimeException("Rejected"); }
...
}
public void run() {
while (!(shutdownRequested && queue.isEmpty())) { ... }
}
◦ shutdown 要求後は新規のリクエストを受け付けない
◦ queue に残っているリクエストの処理を待って終了する
おわり
質疑応答
2019-08-08 社内勉強会 35

Active Object