The Java EE 7 Tutorialの56 Concurrency Utilities for Java EEのセクションを読んで訳した。
56 Concurrency Utilities for Java EE
このチャプターではJSR 236に記述されているConcurrency Utilities for Java EEについて解説しており、内容は以下のトピックの通りです。
- Concurrency Basics
- Main Components of the Concurrency Utilities
- Concurrency and Transactions
- Concurrency and Security
- The jobs Concurrency Example
- The taskcreator Concurrency Example
- Further Information about the Concurrency Utilities
56.1 Concurrency Basics
コンカレンシー(Concurrency)*1とは、同時(並行)に二つ以上のタスクを実行する概念のことを指します。タスクは、メソッド(関数)、プログラムの一部分、もしくはそれと同等な他のプログラム、から構成されます。現在のコンピューターアーキテクチャーでは、マルチコアやシングルCPUでのマルチプロセスのサポートはごく普通となっています。
Javaプラットフォームは常にコンカレントプログラミングのサポートを提供し続けており、これはJava EEコンテナによって提供される多くのサービスを実装するための基礎となっています。Java SE 5では、コンカンレンシーのための高レベルAPIのサポートがjava.util.concurrent
パッケージとして追加されました。
Java EE 7より前では、エンタープライズの開発者がコンカレンシーのユーティリティを安全で標準的な作法で使用できる仕様は存在しませんでした。Java EEのwebとEJBコンテナは、コンテナ管理のスレッドプールを使用してオブジェクトをインスタンス化します。それゆえ、Thread
オブジェクトのインスタンス化にJava SE concurrent APIを使うことは強く非推奨でした。もし開発者が新しい(非コンテナ管理の)Thread
オブジェクトを生成する場合、他のJava EEプラットフォームのサービス(たとえばトランザクションとセキュリティ)下にそのThread
が入ることを保障出来ません。
56.1.1 Threads and Processes
コンカレンシーの主な概念は二つあり、プロセス(processes)とスレッド(threads)です。
プロセスは主にOS上で実行するアプリケーションに関連付けされています。プロセスはOSの下位層とやり取りしてメモリ等のリソースを割り当てるためにランタイムリソースを規定するもので、JVMはプロセスの一例です。
Javaプログラミング言語とプラットフォームではスレッドが主要な関心事です。
実行環境やOSのリソースを消費するために、スレッドはプロセスでいくつかの機能を共有します。しかし、スレッドはプロセスに比べるとかなり少ないリソース消費で生成できます。
スレッドは軽量なので、2~3コアで数GB程度のメモリを持つ近代的なCPUは単一のJVMプロセスで1000スレッドを動作可能です。生成可能な正確なスレッド数については、CPU・OS/利用可能なメモリ・JVMの正しい(チューニングされた)設定、に依存します。
コンカレントプログラミングは多くの問題を解決してアプリケーションのパフォーマンスを向上しますが、複数の実行ライン(スレッドやプロセス)は深刻な問題を引き起こす可能性も多々あります。そうした問題のいくつかを下記に挙げます。
56.2 Main Components of the Concurrency Utilities
コンカレントなリソース(Concurrent resources)とは、Java EEアプリケーションにコンカレンシーの互換性を提供するマネージド・オブジェクトのことです。GlassFIshサーバでは、コンカレントなリソースを設定し、servletやEJBなどのアプリケーションコンポーネントで使用出来ます。コンカレントなリソースはJNDIルックアップやリソース・インジェクション経由で取得します。
concurrency utilitiesの主要なコンポーネントは以下の通りです。
ManagedExecutorService
: managed executor serviceは、アプリケーションがサブミットされたタスクを非同期に実行するために使用します。タスクはコンテナによって開始・マネージドされるスレッド上で実行されます。コンテナのコンテキストはタスクを実行するスレッドに伝播します。
たとえば、ManagedExecutorService.submit()
をGenerateReportTaskなどのタスク を後で実行するためにサブミット可能で、その後、Future
のコールバックで結果が利用可能になった時点で値を取得します。ManagedScheduledExecutorService
: managed scheduled executor serviceは、アプリケーションが指定時刻にサブミットされたタスクを非同期に実行するために使用します。タスクはコンテナによって開始・マネージドされるスレッド上で実行されます。コンテナのコンテキストはタスクを実行するスレッドに伝播します。このAPIはユーザがアプリケーションでプログラミング的にタスク実行を日付/時刻指定でセット可能なスケジューリング機能を提供します。ContextService
: context serviceは、コンテナのコンテキストをキャプチャする動的プロキシ・オブジェクトを生成するために使用し、これによって、アプリケーションが後でコンテキスト内で実行したり、Managed Executor Serviceにサブミット出来るようになります。コンテナのコンテキストはタスクを実行するスレッドに伝播します。ManagedThreadFactory
: managed thread factoryは、アプリケーションがマネージド・スレッドを生成するために使用します。タスクはコンテナによって開始・マネージドされるスレッド上で実行されます。コンテナのコンテキストはタスクを実行するスレッドに伝播します。また、このオブジェクトは特定ユースケース用(カスタム・スレッド付の)カスタム・ファクトリの提供に使用可能です。これは、たとえば、固有ないしプロプライエタリなプロパティをオブジェクトに設定します。
56.3 Concurrency and Transactions
トランザクションの最も基本的な操作はコミットとロールバックですが、コンカレント処理を用いる分散環境では、コミットやロールバック操作が正常に処理されたことを保障するのは難しく、また、トランザクションが異なるスレッド・CPUコア・物理マシン・ネットワークに分散することも可能です。
そうした状況でロールバック操作が正常に実行されることを保証するのは極めて重要です。
Concurrency Utilitiesはjavax.transaction.UserTransaction
を通してコンポーネントでトランザクションのサポートと実装をするためにJava Transaction API (JTA)に依存しており、これは開発者にトランザクション境界の明確な管理の仕組みを提供しています。詳細な情報についてはJTA仕様を参照してください。
オプションで、コンテキストオブジェクトはトランザクションの開始・コミット・ロールバックが可能ですが、親コンポーネントのトランザクションは取得出来ません。
以下のコードは、Runnable
タスクでUserTransaction
を取得と開始をしたあと、EJBやDBなどの他のトランザクション・コンポーネントを呼び出して、コミットしています。
public class MyTransactionalTask implements Runnable { UserTransaction ut = ... // JNDIやDIによる取得 public void run() { // トランザクションの開始 ut.begin(); // サービスやEJBの呼び出し myEJB.businessMethod(); // XA JDBCドライバを使用するDBエンティティの更新 myEJB.updateCustomer(customer); // トランザクションのコミット ut.commit(); } }
56.4 Concurrency and Security
Concurrency Utilities for Java EEは多くのセキュリティ上の決定をアプリケーションサーバの実装に委ねます。もしコンテナがセキュリティコンテキストをサポートする場合、コンテキストは実行スレッドに伝播可能です。ContextService
はランタイムの振る舞いををいくつかサポートしており、security
属性がenabledの場合はコンテナのセキュリティ・プリンシパルが伝播します。
56.5 The jobs Concurrency Example
このセクションでは、エンタープライズアプリケーションにおけるconcurrencyの基本機能の使用方法についての超基本的なサンプルの解説をします。Concurrency Utilities for Java EEの主要コンポーネントの一つ、Managed Executor Serviceを特に取り上げます。
このサンプルはpublic APIを公開するRESTful webを想定しており、このAPIは汎用ジョブ実行のサブミットを行います。これらのジョブはバックグラウンドで処理されます。各ジョブは実行の開始と終了時に"Starting"と"Finished" を表示します。また、バックグラウンド処理をシミュレートするために、各ジョブの実行時間は10秒です。
RESTfulサービスは二つのメソッドを公開します。
/token
: 有効なAPIトークンを登録して返すGETメソッドを公開。/process
: クエリパラメータに実行したいジョブの識別子jobID
、カスタムHTTPヘッダーX-REST-API-Key
にリクエストの有効チェック用のトークン、を受け取るPOSTメソッドを公開。
トークンはAPIで提供されるQuality of Service (QoS)の違いを出すために使用します。サービスリクエストにトークンを渡すユーザは同時に複数のジョブを処理可能です。ただし、トークンを渡さないユーザは同時に1ジョブのみ処理可能です。各ジョブは実行に10秒かかるので、トークンの無いユーザは10秒ごとに1度サービス呼び出しが可能です。トークンを渡すユーザはより高速に処理されます。
この違いを作るためには、各リクエストタイプごとに異なる2つのManaged Executor Servicesを使用します。
56.5.1 Running the jobs Example
2つのManaged Executor Servicesの追加設定をGlassFish Serverにしたあと、NetBeans IDEかMavenのどちらかを使用して、jobs
サンプルのビルド・パッケージング・デプロイ・実行を行う手順を説明します。
56.5.1.1 To Configure GlassFish Server for the Basic Concurrency Example
GlassFish Serverの設定は以下のステップで行います。
http://localhost:4848
で管理コンソールを開く。- Resourcesを開く。
- Concurrent Resourcesを開く。
- Managed Executor Servicesをクリックする。
- Managed Executor Servicesページで、新規Managed Executor Servicesページを開くためにNewをクリックする。
- JNDI Nameフィールドに、high-priorityのManaged Executor Serviceを作成するために
MES_High
を入力し、以下の設定を使用する(他の設定はデフォルトのままで良い)。- Thread Priority: 10
- Core Size: 2
- Maximum Pool Size: 5
- Task Queue Capacity: 2
- OKをクリックする。
- Managed Executor Servicesページで再度Newをクリックする。
- JNDI Nameフィールドに、low-priorityのManaged Executor Serviceを作成するために
MES_Low
を入力し、以下の設定を使用する(他の設定はデフォルトのままで良い)。- Thread Priority: 1
- Core Size: 1
- Maximum Pool Size: 1
- Task Queue Capacity: 0
- OKをクリックする。
56.5.1.2 To Build, Package, and Deploy the jobs Example Using NetBeans IDE
- GlassFishサーバが実行中なことを確認(Starting and Stopping GlassFish Serverを参照)
- FileメニューからOpen Projectを選択
- Open Projectダイアログボックスから以下のディレクトリに移動。
tut-install/examples/concurrency
*5 jobs
フォルダを選択。- Open Projectをクリック。
- Projectsタブで
jobs
プロジェクトを右クリックしてBuildを選択。
このコマンドはアプリケーションをビルドしてデプロイします。
56.5.1.3 To Build, Package, and Deploy the jobs Example Using Maven
- GlassFishサーバが実行中なことを確認(Starting and Stopping GlassFish Serverを参照)
- ターミナルで以下に移動:
tut-install/examples/concurrency/jobs
- アプリケーションをビルとしてデプロイするために以下のコマンドを実行。
mvn install
56.5.1.4 To Run the jobs Example and Submit Jobs with Low Priority
low priorityのジョブをサブミットするユーザとしてこのサンプルを実行するには、以下のステップを実行します。
- webブラウザで、以下のURLを入力します。
http://localhost:8080/jobs
- Jobs Clientページで、Enter a JobIDフィールドに1を入力、Enter a Tokenフィールドは空で、Submit Jobをクリックします。 以下のメッセージがページ下部に表示される筈です。
Job 1 successfully submitted
サーバログには以下のメッセージが記録されます。
INFO: Invalid or missing token! INFO: Task started LOW-1 INFO: Job 1 successfully submitted INFO: Task finished LOW-1
これでlow priorityのジョブをサブミットしたことになります。これは、10秒間は別のジョブをサブミット出来ないことを意味します。もしサブミットを試行する場合、RESTful APIはservice unavailable (HTTP 503)をレスポンスし、ページ下部に以下のメッセージが表示されます。
Job 2 was NOT submitted
サーバログには以下のメッセージが記録されます。
INFO: Invalid or missing token! INFO: Job 1 successfully submitted INFO: Task started LOW-1 INFO: Invalid or missing token! INFO: Job 2 was NOT submitted INFO: Task finished LOW-1
56.5.1.5 To Run the jobs Example and Submit Jobs with High Priority
high priorityのジョブをサブミットするユーザとしてこのサンプルを実行するには、以下のステップを実行します。
- webブラウザで、以下のURLを入力します。
http://localhost:8080/jobs
- Jobs Clientページで、Enter a JobIDフィールドに1から10を入力します。
- トークンを得るために"Get a token here"のhereリンクをクリックします。新しいタブにページが開かれてトークンが表示されます。
- トークンをコピーしてJobs Clientページに戻ります。
- Enter a Tokenフィールドにトークンをペーストし、Submit Jobをクリックします。 以下のようなメッセージがページ下部に表示される筈です。
Job 11 successfully submitted
サーバログには以下のメッセージが記録されます。
INFO: Token accepted. Execution with high priority. INFO: Task started HIGH-11 INFO: Job 11 successfully submitted INFO: Task finished HIGH-11
これでhigh priorityのジョブをサブミットしたことになります。これは、トークンによって複数のジョブをサブミット可能なことを意味し、low priorityをサブミットするときのジョブの10秒制限はありません。連続してトークン付きで3つのジョブをサブミットしたとすると、以下のようなメッセージがページ下部に表示されます。
Job 1 was submitted Job 2 was submitted Job 3 was submitted
サーバログには以下のメッセージが記録されます。
INFO: Token accepted. Execution with high priority. INFO: Task started HIGH-1 INFO: Job 1 successfully submitted INFO: Token accepted. Execution with high priority. INFO: Task started HIGH-2 INFO: Job 2 successfully submitted INFO: Task finished HIGH-1 INFO: Token accepted. Execution with high priority. INFO: Task started HIGH-3 INFO: Job 3 successfully submitted INFO: Task finished HIGH-2 INFO: Task finished HIGH-3
56.6 The taskcreator Concurrency Example
taskcreator
サンプルは、Concurrency Utilities for Java EEを使用してタスクの即時・定期・固定時間後、に実行する方法の例です。このサンプルには、ユーザがタスクを実行するためにサブミットとタスクごとのメッセージ情報を表示する、JavaServer Facesインタフェースがあります。タスクの即時実行にはManaged Executor Serviceを使用し、タスクの定期および固定時間後実行にはManaged Scheduled Executor Serviceを使用します。(これらのサービスについてはMain Components of the Concurrency Utilitiesを参照してください。)
taskcreator
は以下のコンポーネントから構成されます。
- JavaServer Facesページ(
index.xhtml
)は三つの要素から構成されています。タスクをサブミットするためのフォーム、タスクの実行ログ、定期タスクをキャンセルするためのフォーム。このページはタスクの生成とキャンセルにAjaxリクエストをサブミットします。また、タスク実行ログを更新するためにJavaScriptを使用してWebSocketメッセージを受け取ります。 - CDI managed bean(
TaskCreatorBean
)はJavaServer Facesページからのリクエストを処理します。このbeanは新しいタスクのサブミットと定期タスクをキャンセルするためにTaskEJB
のメソッドを実行します。 - EJB(
TaskEJB
)はresource injectionでexecutor serviceのインスタンスを取得して、実行するタスクをサブミットします。また、このbeanはJAX-RS webサービスのエンドポイントです。タスクはこのエンドポイントに情報メッセージを送信します。 - WebSocketエンドポイント(
InfoEndpoint
)はクライアントにメッセージ情報を送信するためにEJBが使用します。 - タスクのクラス(
Task
)はRunnable
インタフェースを実装します。このクラスのrun
メソッドはTaskEJB
のwebサービス・エンドポイントに情報メッセージを送信し、1.5秒スリープします。
Figure 56-1はtaskcreator
サンプルのアーキテクチャを図で示しています。
TaskEJB
クラスは以下のようにしてアプリケーションサーバからデフォルトのexecutor serviceオブジェクトを取得します。
@Resource(name="java:comp/DefaultManagedExecutorService") ManagedExecutorService mExecService; @Resource(name="java:comp/DefaultManagedScheduledExecutorService") ManagedScheduledExecutorService sExecService;
TaskEJB
のsubmitTask
メソッドは以下のようにして実行したいタスクをサブミットするために上記のオブジェクトを使用します。
public void submitTask(Task task, String type) { /* アプリケーションサーバのmanaged executorオブジェクトを使用 */ switch (type) { case "IMMEDIATE": mExecService.submit(task); break; case "DELAYED": sExecService.schedule(task, 3, TimeUnit.SECONDS); break; case "PERIODIC": ScheduledFuture<?> fut; fut = sExecService.scheduleAtFixedRate(task, 0, 8, TimeUnit.SECONDS); periodicTasks.put(task.getName(), fut); break; } }
定期タスクでは、TaskEJB
はScheduledFuture
オブジェクトの参照を保持し続けるので、ユーザはいつでもタスクキャンセル可能です。
56.6.1 Running the taskcreator Example
このセクションではNetBeans IDEかMavenのどちらかを使用して、taskcreator
サンプルのビルド・パッケージング・デプロイ・実行を行う手順を説明します。
56.6.1.1 To Build, Package, and Deploy the taskcreator Example Using NetBeans IDE
- GlassFishサーバが実行中なことを確認(Starting and Stopping GlassFish Serverを参照)
- FileメニューからOpen Projectを選択
- Open Projectダイアログボックスから以下のディレクトリに移動。
tut-install/examples/concurrency
*6 taskcreator
フォルダを選択。- Open Projectをクリック。
- Projectsタブで
taskcreator
プロジェクトを右クリックしてBuildを選択。
このコマンドはアプリケーションをビルドしてデプロイします。
56.6.1.2 To Build, Package, and Deploy the taskcreator Example Using Maven
- GlassFishサーバが実行中なことを確認(Starting and Stopping GlassFish Serverを参照)
- ターミナルで以下に移動:
tut-install/examples/concurrency/taskcreator
- アプリケーションをビルとしてデプロイするために以下のコマンドを実行。
mvn install
56.6.1.3 To Run the taskcreator Example
1.webブラウザーで以下のURLを開きます。
http://localhost:8080/taskcreator/
このページには、タスクをサブミットするためのフォーム・タスクの実行ログ・定期タスクをキャンセルするためのフォーム、があります。
2.Immediateタスク・タイプを選択し、タスク名を入力して、Submitボタンをクリックします。タスク実行ログに以下のようなメッセージが表示されます。
12:40:47 - IMMEDIATE Task TaskA finished 12:40:45 - IMMEDIATE Task TaskA started
3.Delayed (3 sec)タスク・タイプを選択し、タスク名を入力して、Submitボタンをクリックします。タスク実行ログに以下のようなメッセージが表示されます。
12:43:26 - DELAYED Task TaskB finished 12:43:25 - DELAYED Task TaskB started 12:43:22 - DELAYED Task TaskB submitted
4.Periodic (8 sec)タスク。タイプを選択し、タスク名を入力して、Submitボタンをクリックします。タスク実行ログに以下のようなメッセージが表示されます。
12:45:25 - PERIODIC Task TaskC finished run #2 12:45:23 - PERIODIC Task TaskC started run #2 12:45:17 - PERIODIC Task TaskC finished run #1 12:45:15 - PERIODIC Task TaskC started run #1
1つ以上の定期タスクを追加できます。定期タスクをキャンセルするには、フォームでタスクを選択してCancel Taskをクリックします。
56.7 Further Information about the Concurrency Utilities
concurrencyのより詳細な情報については、下記を参照してください。
- Concurrency Utilities for Java EE仕様
http://jcp.org/en/jsr/detail?id=236 - Concurrency Utilities仕様
http://jcp.org/en/jsr/detail?id=166 - The Java TutorialsのConcurrency Lesson
http://docs.oracle.com/javase/tutorial/essential/concurrency/
関連エントリ
- The Java EE 7 Tutorialのテキトー翻訳まとめ - Qiita - Java EE 7 Tutorialのうち、自分がテキトー翻訳したものの一覧
- JSR 236 1.0 Concurrency Utilities forJava-EEをテキトーに翻訳した - kagamihogeの日記
*1:素直に『同時並行性』とでも訳せばいいのだけど。この文書ではダサいの覚悟の上で『コンカレンシー』とそのままにする。
*2:たとえば、プライオリティの設定がマズくて、あるスレッドに一向に処理の優先順位が回ってこなくなること。『java並行処理プログラミング』p.245 10-3-1 飢餓状態 参照
*3:おそらく、Javaでいうところのsynchronizedを上手くつけないと甚大な速度低下を生むが、無いと共有リソースの状態が極めて不安定な謎バグを生むようなこと、とかを指してると思われる
*4:おそらく、たとえば、Servletにインスタンス変数でカウンタを実装しようとする、アトミックでないが故に不正確なデータを生成するようになる状況のこと、と思われる
*5:https://java.net/projects/javaeetutorial/sources/svn/show/trunk/examples/concurrency
*6:https://java.net/projects/javaeetutorial/sources/svn/show/trunk/examples/concurrency