Concurrency Utilities for Java EEをつかってみる

  • 25
    Like
  • 0
    Comment
More than 1 year has passed since last update.

これはJava EE Advent Calendar 2014の11日目の記事です。昨日は@tkxlabさんのJavaEEをはじめよう!でした。明日は@tatsu983さんです。

先日のJJUG CCC 2014 Fallでは,JSR 356 Java API for WebSocket 1.0についてお話させていただきましたが,今回はJSR 236 Concurrency Utilities for Java EEについてです。

Concurrency Utilities for Java EEについて

Javaの並行処理の歴史

Javaにおける並行処理,つまりマルチスレッドによる同時実行の歴史については,9月のJJUGのナイトセミナーでさくらばさんが話されたLegend of Java Concurrency/Parallelismがとてもいいまとめになっていますので,ぜひごらんください。

Java SEの世界では比較的自由なスレッド生成・並行処理ですが,Java EEの世界では長らくタブーとされていました。EJB仕様ではユーザーコードによるスレッドの生成が明示的に禁止されていましたし,Servletからのスレッド生成にもいろいろな制限がありました。ほとんどのアプリケーションサーバーでは,Servletの実行スレッドからHttpSessionやHttpServletRequestなどを他のスレッドに渡しても,それらの機能が正常に呼び出せません。またDataSourceから取得したConnectionを複数スレッドで使い回したりすると,トランザクションが正常に管理されなくなるなどの問題が起こります。JNDIで利用されるjava:comp/envなどの名前空間も,独自に作成したスレッドからは参照ができません。

これは,Java EEの世界で利用される様々な情報を管理している「コンテキスト」の多くが,スレッドに紐付けられて管理されているためです。Java EEの機能の多くはこのコンテキストに依存しているため,別のスレッドに処理が移ると正常に稼働しなくなるのです。

Java EEでの非同期処理の歴史

アプリケーションサーバー上でリクエスト処理とは異なるスレッドで非同期処理をおこなう方法は,過去にいくつも登場しています。J2EE 1.3では,Message-oriented Middlewareからとどいたメッセージをトリガーに処理をおこなうMDB(Message Driven Bean)がEJBに追加されました。Java EE 6では,Servletから非同期処理をおこなうAsyncContext機能がServlet 3.0仕様に,EJBで非同期処理をおこなうAsynchronous機能がEBJ 3.1仕様に追加されました。

汎用的な非同期処理をJava EE環境で使用するための仕様としては,2003年からJSR 236でTimer for Application Serverという名前で検討が始まりました。当初はJava EE 5に組み込まれる予定だったのですが,別のJSRと統合されるなど紆余曲折があり,最終的にConcurrency Utilities for Java EEと名前を変えてJava EE 7に組み込まれました。

Concurrency Utilities for Java EE

Concurrency Utilities for Java EEは,その名前が示すとおりJava2 SE 5で追加されたConcurrency Utilitiesを拡張する形で策定されました。SE環境で利用できるExecutorServiceやScheduledExecutorServiceを拡張したスレッド管理機能が提供され,ライフサイクル管理やモニタリング機能が提供されます。また,Java EEのコンテキストを伝達するContextServiceが提供されます。

ちなみにEnterprise JavaBean→EJBのような公的な略称がつくられなかったため,毎回「Concurrency Utilities for Java EE」と書かなければいけない,ちょっと面倒な仕様名です。前半の「Concurrency Utilities」だけだと,SE環境で提供されているjava.util.concurrentの機能のことになってしまいますし…。

Concurrency Utilities for Java EEについての詳細なガイドは,2015年1月にdeveloperWorksのサイト上で「Java EE 7 アプリケーション設計ガイド - Concurrency Utilities編」が公開されていますので,そちらの方もご覧下さい。

Concurrency Utilities for Java EEをつかってみる

別スレッドによる非同期処理に用途は数多くありますが,代表的なのは「時間のかかる処理,たとえば負荷の高いデータベース処理を別スレッドで非同期に実行」というものでしょう。データベースのConnectionを取得するDataSourceをJNDIでリソース参照経由でlookupしたり,トランザクション管理のためのUserTransactionを取得するにはJava EEのコンテキストが必要です。これらをConcurrency Utilities for Java EEで実装してみます。

サーバー環境を用意する

さて,この記事ではWebSphere LibertyプロファイルをつかってConcurrency Utilities for Java EEをためしてみます。Libertyプロファイルを使ったことがない,というかたはCodezineの記事とかdeveloperWorksの記事とかこちらの記事とかを参考に導入してみて下さい。

Libertyプロファイルでは,おととい12月9日にConcurrency Utilities for Java EEをはじめとしたJava EE 7の一部の機能が正式版として公開されました。本番環境で商用サポート付きでご利用いただくことができます。

現在公開されている最新版のVersion 8.5.5.4であれば最初からのConcurrency Utilities for Java EEのFeatureが入っています。8.5.5.2〜8.5.5.3であれば導入ディレクトリのbinにあるfeatureManagerコマンドを使用して簡単にFeatureを追加することができます。

$ ./featureManager install concurrent-1.0 --when-file-exists=ignore

サーバーを構成する

Libertyプロファイルの構成ファイルのserver.xmlにconcurrent-1.0のFeatureを追加すると,Concurrency Utilities for Java EEの機能が有効になります。

server.xml
<featureManager>
  <feature>concurrent-1.0</feature>
</featureManager>

サーバーで提供されるExecutorServiceを構成します。今回はJava EEメタデータを管理するコンテキストを伝搬させたいのでjeeMetadataContextを構成します。このほかにもclassloaderContextやsecurityContextなどが構成できます。

server.xml
<managedExecutorService jndiName="concurrent/myExecutor">
  <contextService>
    <jeeMetadataContext />
  </contextService>
</managedExecutorService>

Javaコードから利用する

構成したExecutorServiceをサーブレットから利用するには,java.util.concurrent.ExecutorServiceやjavax.enterprise.concurrent.ManagedExecutorServiceのインスタンス変数を用意して,@Resourceをつかってインジェクションすることができます。

@Resource(lookup="concurrent/myExecutor")
private ExecutorService myExecutor;

ローカル変数として利用する場合は,JNDIをつかってlookupすることもできます。

ExecutorService myExecutor;
try {
    myExecutor = (ExecutorService)new InitialContext().lookup("concurrent/myExecutor");
} catch (NamingException e) {
    e.printStackTrace();
}

ExecutorServiceで実行されるタスクを定義しておきます。この中では,JNDIからUserTransactionを取得してトランザクション処理をおこなってみます。通常のExecutorServiceで実行されたタスクであれば,java:comp/envの名前空間を利用することはできないのですが,Concurrency Utilities for Java EEのExecutorServiceで実行すると,ちゃんとコンテナが提供するUserTransactionが取得できます。アプリケーションに定義されたリソース参照などにもアクセスできます。

LongTask.java
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.transaction.SystemException;
import javax.transaction.UserTransaction;

public class LongTask implements Runnable {

    @Override
    public void run() {
        UserTransaction ut = null;
        try {
            Context con = new InitialContext();
            ut = (UserTransaction)con.lookup("java:comp/UserTransaction");
            ut.begin();
            // some transactional operations
            //
            ut.commit();
        } catch (Exception e) {
            if (ut != null) try {
                ut.rollback();
            } catch (SystemException se) {}
        }
    }
}

このタスクをServletの中から実行します。もちろん,Feature<T>などを利用して結果を受け取ることもできます。

myExecutor.submit(new LongTask());

おわりに

Concurrency Utilities for Java EEは,JSF 2.2やCDI 1.1とならんで,Java EE 7の目玉機能のひとつです。簡潔で簡単に利用できる仕様でありながら,いままでできなかった多くの要件を実現することができます。

商用アプリケーションサーバーでのサポートもはじまりましたので,ぜひご利用を検討下さい。