Weitere ähnliche Inhalte Ähnlich wie Java concurrency in_practice_chap06 (20) Java concurrency in_practice_chap062. 6章の内容
•(6-1) 2つの方式の紹介とその欠点
・sequential approach
poor responsiveness and throughput
・thread per task approach
poor resource management
•(6-2) Executor Framework
–実行ポリシー
–スレッドプール
–Executorのライフサイクル
–タスクの遅延開始と周期的実行
・(6-3)並列化できる箇所/すべき箇所を見つける
–ページレンダラの例
–Callable and Future
–異質なタスクを並列化する限界
–CompletionService
–タスクに時間制限を設ける
4. 6-1-1 タスクを逐次的に実行する
•List 6-1 Sequential Web Server.
–1度に1つのリクエストしか扱えない
–サーバが1つのリクエストを処理する間、新たな接続は待たされる
–スループットも応答性も得られない
–GUIフレームワークでは、single thread で sequentially なタスクに利点
(9章)
class SingleThreadWebServer {
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(80);
while (true) {
Socket connection = socket.accept();
handleRequest(connection);
}
5. 6-1-2. タスクのためのスレッドを明示的に作る
•List 6-2 Web Server that Starts a New Thread for Each Request.
–main loop が クライアントからの接続毎に new thread を作る
•new thread がリクエストを処理
•main thread はリクエストを処理しない
class ThreadPerTaskWebServer {
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(80);
while (true) {
final Socket connection = socket.accept();
Runnable task = new Runnable() {
public void run() {
handleRequest(connection);
}
};
new Thread(task).start();
}
8. 6-2 Executor フレームワーク
•5章(5-3 p102)で サイズ制限のあるキュー (bounded queues)
を使用した
–過負荷でメモリ不足にならないために
–Thread pool はそれと同じ考え方でスレッドを管理する
•java.util.concurrent パッケージは、Executor フレームワークの
一部として、柔軟なThread poolの実装を提供
9. 6-2 Executor フレームワーク
•List 6-3 Executor Interface.
•taskの submission(依頼) と execution(実行) を分離
–producer consumer pattern (5-3)
public interface Executor {
void execute(Runnable command);
}
10. 6-2-1Executor を使ったWebサーバ
• List 6-4. Web Server Using a Thread Pool.
class TaskExecutionWebServer {
private static final int NTHREADS = 100;
private static final Executor exec
= Executors.newFixedThreadPool(NTHREADS);
public static void main(String[] args) throws IOException {
ServerSocket socket = new ServerSocket(80);
while (true) {
final Socket connection = socket.accept();
Runnable task = new Runnable() {
public void run() {
handleRequest(connection);
}
};
exec.execute(task);
}
11. 6-2-1Executor を使ったWebサーバ
•thread per task approach の動作にするExecutorの例
–List 6-5
•sequential approach の動作にするExecutorの例
–List 6-6
public class ThreadPerTaskExecutor implements Executor {
public void execute(Runnable r) {
new Thread(r).start();
};
}
public class WithinThreadExecutor implements Executor {
public void execute(Runnable r) {
r.run();
};
}
12. 6-2-2 実行ポリシー
•タスクの実行の what, where, when, how
–タスクを
•どのスレッドで実行するか?
•どんな順序で実行するか?(FIFO, LIFO, priority order)
•並行に実行する数は?
•キューに並ばす最大数は?
•過負荷でrejectしなければいけない時にどのタスクを犠牲にする
か? アプリケーションの通知はどうするか?
•実行前後でどんなアクションを行うべきか?
•実行ポリシーの仕様を、タスクの依頼から分離
–実働機 のリソース状況にマッチした実行ポリシーを指定することが困難ではなく
なる。
13. 6-2-3 スレッドプール
•worker threads の均質な(一種類のスレッドから成る)
プールを管理
•利点
–既存の thread の再利用
–thread 作成の待ち時間がタスクの実行を遅らせない→
応答性向上
–thread pool サイズの適切な tuning
•→ プロセッサを無駄に遊ばせない
•→ メモリ不足にならない
14. 6-2-3 スレッドプール
Executors の static ファクトリメソッド
•newFixedThreadPool
•固定サイズ
•スレッドを最大プールサイズまで作る
•プールサイズを一定に保つ
•return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new
LinkedBlockingQueue<Runnable>())
•newCachedThreadPool
•more flexibility
•idle threads は刈り取る
•要求が増えたら new threads を追加
•プールのサイズ制限が無い
•return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new
SynchronousQueue<Runnable>());
15. 6-2-3 スレッドプール
Executors の static ファクトリメソッド
•newSingleThreadExecutor
•single worker thread
•task queue の順序(FIFO, LIFO, priority order)に応じて、 sequentially に処理す
る事が保証されている。
•return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
•newScheduledThreadPool
•固定サイズ
•遅延開始、周期的実行をサポート
•Timerに似ている (→ 6-2-5 参照)
•return new ScheduledThreadPoolExecutor(corePoolSize);
→ThreadPoolExecutorの構成については第8章参照
16. 6-2-4 Executor のライフサイクル
•graceful shutdown
•開始した仕事は無事に終わるが、新たな仕事は受け付けない
•abrupt shutdown
•唐突なshutdown。電源OFF。
•ExecutorService
•Executor を extends した interface
•ライフサイクルを管理するメソッドが定義されている
•List 6-7. Lifecycle Methods in ExecutorService.
public interface ExecutorService extends Executor {
void shutdown();
List<Runnable> shutdownNow();
boolean isShutdown();
boolean isTerminated();
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
17. 6-2-4 Executor のライフサイクル
ExecutorService が暗黙的に定義しているライフサイクル
•ExecutorService の ライフサイクルの3つのステート
•running
•shutting down
•terminated
•shutdown メソッド
•graceful shutdown
•新たなタスクは受け付けない
•前に依頼されたタスク(実行を開始していないものも含めて)は完了可能
→ 終了するまで待つ
•shutdownNow メソッド
•abrupt shutdown
•実行中のタスクのキャンセルを試みる
•キューにあってまだ始まっていないタスクはスタートしない
18. 6-2-4 Executor のライフサイクル
•シャットダウンの後に依頼されたタスク
•rejected execution handler (8-3-3)が取り扱う
•黙ってタスクを捨てる
or
•RejectedExecutionException をthrowする
•全てのタスクが完了したら
→ ExecutorService は terminated ステートに遷移
•awaitTermination メソッド
•ExecutorService が terminated ステートへ到達するのを待つ
•(apiリファレンス) 「シャットダウン要求後にすべてのタスクが実行を完了していたか、タイムアウトが発生するか、現
在のスレッドで割り込みが発生するか、そのいずれかが最初に発生するまでブロックします」
•isTerminated メソッド
•ExecutorService が terminated ステートへ到達したか否かをチェック
•(apiリファレンス)「シャットダウンに続いてすべてのタスクが完了していた場合、true を返します。 」
•shutdown に続いて awaitTermination を呼ぶのはよく行われる
→ シャットダウンに関しては第7章で
19. 6-2-4 Executor のライフサイクル
•ライフサイクルのサポートを加えた Webサーバの例
•List 6-8. Web Server with Shutdown Support.
class LifecycleWebServer {
private final ExecutorService exec = ...;
public void start() throws IOException {
ServerSocket socket = new ServerSocket(80);
while (!exec.isShutdown()) {
try {
final Socket conn = socket.accept();
exec.execute(new Runnable() {
public void run() { handleRequest(conn);
});
} catch (RejectedExecutionException e) {
if (!exec.isShutdown())
log("task submission rejected", e);
}
}
}
public void stop() { exec.shutdown(); }
void handleRequest(Socket connection) {
Request req = readRequest(connection);
if (isShutdownRequest(req))
stop();
else
dispatchRequest(req);
}
20. 6-2-5 タスクの遅延開始と周期的実行
•(×)java.util.Timer にはいくつか欠点がある
→ (○)ScheduledThreadPoolExecutor を代わりに使うように。
•Timerの欠点
•1つのTimerで1つのスレッド。
•複数のTimerTaskを実行する時
→ 1つの TimerTask の実行が長すぎると
→ 他のタスクが不正確になる
※ ScheduledThreadPoolExecutor は複数スレッドを使って回避する
•システムクロックの変更に影響を受けてしまう
•TimerTask が非チェック例外をthrowした時の対応がお粗末
•Timer スレッドがcatchしない
•Timer スレッドが終了してしまう。Timer全体がキャンセルされたと誤判断
•"thread leakage" と呼ばれる問題
•(スケジューリング済みで未実行のタスクが実行されない、新たなタスクはスケジュールされない)
•→ 7-3 で避ける方法を説明
21. 6-2-5 タスクの遅延開始と周期的実行
•List 6-9 Timer の動作をデモするクラス
•DelayQueue
•scheduling service を自作する場合に使える
•BlockingQueue の実装クラス
•ScheduledThreadPoolExecutorのようなスケジューリング機能
public class OutOfTime {
public static void main(String[] args) throws Exception {
Timer timer = new Timer();
timer.schedule(new ThrowTask(), 1);
SECONDS.sleep(1);
timer.schedule(new ThrowTask(), 1); // “Timer already cancelled”
SECONDS.sleep(5);
}
static class ThrowTask extends TimerTask {
public void run() { throw new RuntimeException(); }
}
23. 6-3-2 結果を返すタスク : Callable and Future
•Runnable run メソッド
•値を返せない
•チェックされる例外をthrowできない
•Callable call メソッド
•値を返せる
•チェックされる例外をthrowできる
•Executor が実行するタスクのlifecycle
•4つの段階:
•created
•submitted
•started
•completed
•Executor framework でのタスクのキャンセル (※キャンセルについては第7章で)
•submit されたけれど、started になってないタスク
→ いつでもキャンセル可能
•started のタスク
→ interruption に応答する場合キャンセル可能
•completed のタスク
→ 何も起きない
24. 6-3-2 結果を返すタスク : Callable and Future
•Future
•タスクのlifecycleを表現
•前に進めるだけ、後戻りはできない
•ExecutorService のlifecycleと同様
•調べるメソッド
•タスクが completed かどうか
•タスクが cancel されたかどうか
•タスクの結果を取り出すメソッド
•List 6-11 Callable and Future Interfaces.
public interface Callable<V> {
V call() throws Exception;
}
public interface Future<V> {
boolean cancel(boolean mayInterruptIfRunning);
boolean isCancelled();
boolean isDone();
V get() throws InterruptedException, ExecutionException,
CancellationException;
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException,
CancellationException, TimeoutException;
25. 6-3-2 結果を返すタスク : Callable and Future
•Future.get の振る舞い
•タスクのstateによって異なる
•completed
•直ちにreturnする
or
•Exceptionをthrow
•完了していなければ、完了するまでブロック
•Exceptionをthrowして完了の場合
•get は ExecutionException でラップしてrethrowする
→ getCause メソッドで元の例外を取得できる
•cancelled
•get は CancellationException を throwする
26. 6-3-2 結果を返すタスク : Callable and Future
•Future を作成する方法
•ExecutorService の submit メソッド
•FutureTask のインスタンスを明示的に作成
•AbstractExecutorService の newTaskFor メソッド
•defaultの実装は FutureTask をnewしてるだけ
•overrideする事でFuture のインスタンスの作り方を制御できる(java6から)
•List 6-12 Default Implementation of newTaskFor in ThreadPoolExecutor.
(参考: クラス図)
http://blog.thihy.info/wp-content/uploads/2013/01/%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%BB%A7%E6%89%BF%E5%85%B3%E7%B3%BB.jpg
(参考: FutureTaskクラスがJava SE 6で変更)
http://itpro.nikkeibp.co.jp/article/COLUMN/20071001/283396/
protected <T> RunnableFuture<T> newTaskFor(Callable<T> task) {
return new FutureTask<T>(task);
}
27. 6-3-3 例: Future を使うページレンダラ
•2つのタスクに分割
•テキストのrender
•画像のダウンロード → Callable を作って → ExecutorService に submit
•List 6-13 Waiting for Image Download with Future.
public class FutureRenderer {
private final ExecutorService executor = ...;
void renderPage(CharSequence source) {
final List<ImageInfo> imageInfos = scanForImageInfo(source);
Callable<List<ImageData>> task =
new Callable<List<ImageData>>() {
public List<ImageData> call() {
List<ImageData> result
= new ArrayList<ImageData>();
for (ImageInfo imageInfo : imageInfos)
result.add(imageInfo.downloadImage());
return result;
}
};
Future<List<ImageData>> future = executor.submit(task);
renderText(source);
try {
List<ImageData> imageData = future.get();
for (ImageData data : imageData)
renderImage(data);
} catch (InterruptedException e) {
// thread の interrupted status を再確立する
Thread.currentThread().interrupt();
// 結果はいらないので、タスクをcancel
future.cancel(true);
} catch (ExecutionException e) {
throw launderThrowable(e.getCause());
29. 6-3-5 CompletionService:
Executor Meets BlockingQueue
•複数タスクの完了
•タイムアウトが0の Future.get で何度もポーリング
↓ もっと良い方法
•CompletionService を使う
•take や poll メソッドが使える
•ExecutorCompletionService
•CompletionService の実装クラス
30. 6-3-5 CompletionService:
•ExecutorCompletionService のソースの抜粋
public class ExecutorCompletionService<V> implements CompletionService<V> {
private final Executor executor;
private final AbstractExecutorService aes;
private final BlockingQueue<Future<V>> completionQueue;
private class QueueingFuture extends FutureTask<Void> {
QueueingFuture(RunnableFuture<V> task) {
super(task, null);
this.task = task;
}
protected void done() { completionQueue.add(task); } // FutureTask にはtaskが完了したら呼ばれるdoneメソッドがある
private final Future<V> task;
}
…
public ExecutorCompletionService(Executor executor) {
if (executor == null)
throw new NullPointerException();
this.executor = executor;
…
public Future<V> submit(Callable<V> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<V> f = newTaskFor(task); // return new FutureTask<V>(task);
// taskの実行をexecutorに委譲 // taskが依頼されるとtaskはQueueingFutureでラップされる
executor.execute(new QueueingFuture(f));
return f;
}
…
public Future<V> take() throws InterruptedException {
return completionQueue.take();
}
public Future<V> poll() {
return completionQueue.poll();
}
31. 6-3-6 CompletionService を使ったページレンダラ
•List 6-15
•画像1つ1つのダウンロード → 別々のタスク → スレッドプールで実行
public class Renderer {
private final ExecutorService executor;
Renderer(ExecutorService executor) { this.executor = executor; }
void renderPage(CharSequence source) {
final List<ImageInfo> info = scanForImageInfo(source);
CompletionService<ImageData> completionService =
new ExecutorCompletionService<ImageData>(executor);
for (final ImageInfo imageInfo : info)
completionService.submit(new Callable<ImageData>() {
public ImageData call() {
return imageInfo.downloadImage();
}
});
renderText(source);
try {
for (int t = 0, n = info.size(); t < n; t++) {
// takeで取り出す順番は、タスクが完了した順番 // 何も存在しない場合は待機
Future<ImageData> f = completionService.take();
ImageData imageData = f.get();
renderImage(imageData);
}
…
32. 6-3-7 タスクに時間制限を設ける
•例: 広告配信
•ADサーバから2秒以内に取り込めなかったら
•→ デフォルトの広告を表示
•→ 広告の取り込みは中断
•時間指定付きの Future.get
•結果が得られば直ちにreturnする
•タイムアウトまで得られなければ TimeoutException を throw
•get に渡すタイムアウトが負数の場合は0扱い
•タスクのキャンセル
•TimeoutException を catch → future.cancel(true);
→ List 6-13, 6-16 で使用
※第7章
33. 6-3-7 タスクに時間制限を設ける
•List 6-16 Fetching an Advertisement with a Time Budget.
•広告を取り込むタスク → Executorに依頼
Page renderPageWithAd() throws InterruptedException {
long endNanos = System.nanoTime() + TIME_BUDGET;
Future<Ad> f = exec.submit(new FetchAdTask());
// ページを Render しながら 広告を待つ
Page page = renderPageBody();
Ad ad;
try {
// 一定の時間(TIME_BUDGET)だけ待つ
long timeLeft = endNanos - System.nanoTime();
ad = f.get(timeLeft, NANOSECONDS);
} catch (ExecutionException e) {
ad = DEFAULT_AD;
} catch (TimeoutException e) {
ad = DEFAULT_AD;
f.cancel(true);
}
page.setAd(ad);
return page;
34. 6-3-8 例: 旅行予約ポータル
•例: 複数企業からビッドを取り込む
•一定時間内の応答だけ提示
•タスク境界
•1つの起業からのビッド → 独立している → 妥当なタスク境界
•複数企業の数分、タスクをn個
•スレッドプールに依頼
•複数のFutureで管理
•時間指定付きの Future.get
•結果を逐次的に取り込む
↓もっと簡単な方法
•時間指定付きの invokeAll
35. 6-3-8 例: 旅行予約ポータル
•invokeAll
•引数 : a collection of tasks
•戻り値: a collection of Futures
•この2つのcollectionは構造が同じ。
•引数のタスクのcollectionのiteratorの順番で戻り値のcollectionにFutureが加わる
•時間指定付きの invokeAll がリターンするのは
•全てのタスクが完了したとき
•呼び出しているスレッドがinterruptされた時
•タイムアウトになった時
→ 各タスクは
•正常に完了している
or
•キャンセルされている 、かのどちらか。
→ get または isCancelled を読んでどちらの結果なのか判別できる
36. 6-3-8 例: 旅行予約ポータル
•List 6-17 時間制限付きで旅行の見積もりをリクエストする (抜粋)
private class QuoteTask implements Callable<TravelQuote> {
…
public List<TravelQuote> getRankedTravelQuotes( …) {
…
List<QuoteTask> tasks = new ArrayList<QuoteTask>();
for (TravelCompany company : companies)
tasks.add(new QuoteTask(company, travelInfo));
List<Future<TravelQuote>> futures =
exec.invokeAll(tasks, time, unit);
List<TravelQuote> quotes =
new ArrayList<TravelQuote>(tasks.size());
Iterator<QuoteTask> taskIter = tasks.iterator();
for (Future<TravelQuote> f : futures) {
QuoteTask task = taskIter.next();
try {
quotes.add(f.get());
} catch (ExecutionException e) {
quotes.add(task.getFailureQuote(e.getCause()));
} catch (CancellationException e) {
quotes.add(task.getTimeoutQuote(e));
}
}
Collections.sort(quotes, ranking);
return quotes;