JSR 237: Work Manager for Application Servers

つい先日、次期Java EEプラットフォーム仕様となるJSR 316がExecutive Committieeによる最初の承認投票を賛成多数で通過した。JSR 316では、Java EE 6で新しく追加されるAPIとしてJSR 196、JSR 236、JSR 237、JSR 299、JSR 311などを挙げている。そのうちJSR 311については本連載でも第18回で取り上げた。今回は「JSR 237: Work Manager for Application Servers」を紹介したい。

JSR 237は、ひとことで言ってしまえばJava EE環境でスレッドプログラミングを可能にするためのAPIである。Javaでスレッドプログラミングを行うためには、通常java.lang.Runnableインタフェースやjava.lang.Threadクラスを使用する。しかし、Java EE環境では事情が違ってくる。サーブレットやEJBの管理対象環境ではこれらのAPIによるスレッド処理には制約があり、原則として推奨されていないからだ。

JSR 237では、Java EE環境においても安全にスレッドを使用した非同期並列処理を実現できる手段を提供する。この仕様の策定は、IBMとBEA Systemsが中心になって行っている。両社はJ2EEアプリケーションサーバのプログラミングモデルとAPIの仕様を共同で策定するCommonJプロジェクトを進めており、その一環として非同期並列処理のための「Timer and Work Manager for Application Servers」を定めた。JSR 237は、そのうちの"Work Manager"の部分を標準化する目的でJCPに提出されたものである。

JSR 237のドラフトはまだ公開されていないため、今回はCommonJの実装を使ってJava EE環境上でスレッドプログラミングを行ってみる。

CommonJのWork Managerを試す

CommonJの「Timer and Work Manager for Application Servers」仕様やインタフェース実装はBEAによるプロジェクトサイトまたはIBMによるプロジェクトサイトで公開されている。また、これらのAPIは「IBM WebSphere Application Server V6.0以降」および「BEA WebLogic Server 9.0以降」に実装されているほか、オープンな実装としてはGlobus Toolkitなどがある。

CommonJのWork Managerのクラス/インタフェースはcommonj.workパッケージ以下に用意されている。ここにjava.lang.RunnableをextendsしたWorkインタフェースが定義されており、これがJava EE用のThreadクラスとも言うべきものになる。したがってスレッドの処理はWorkインタフェースをimplementsしたクラスのrun()メソッドに記述すればよい。

スレッドの実行はWorkManagerのschedule()メソッドにWorkオブジェクトを渡すことで行う。WorkManagerオブジェクトはアプリケーションサーバよりJNDI経由で取得する。したがって、まず準備段階としてアプリケーションサーバのネーミングサービスにWorkManagerオブジェクトを登録しなくてはならない。WorkManagerオブジェクトの登録の仕方は各アプリケーションサーバによって異なるので、それぞれのマニュアルを参考にしてほしい。たとえばBEA Weblogic Serverの場合は、管理コンソールの[Environment]-[Work Managers]メニューからWorkManagerの作成と登録が行える(図1)。

図1 BEA Weblogic ServerにおけるWorkManagerの登録

続いてJava EEコンポーネントのDeployment Descriptorファイル(Servletの場合web.xml、EJBの場合ejb-jar.xml)にリソース参照の設定を追加する。今回はServletを使うのでweb.xmlにリスト1のように記述する。

リスト1 web.xml - リソース参照の設定を追加

    <resource-ref>       
        <res-ref-name>wm/MyWorkManager</res-ref-name>       
        <res-type>commonj.work.WorkManager</res-type>       
        <res-auth>Container</res-auth>      
        <res-sharing-scope>Shareable</res-sharing-scope>        
    </resource-ref> 

Workインタフェースの実装クラスSampleWorkはリスト2のように、呼び出されたら指定されたミリ秒だけスリープするスレッドとした。なおisDaemon()メソッドがtrueを返す場合、そのスレッドはデーモンスレッドとして動作する。

リスト2 SampleWork.java - Workの実装クラス。単一スレッドとして動作する

package sample;

import commonj.work.Work;

public class SampleWork implements Work {
    protected String name = "noname";
    protected long time = 1000;

    public SampleWork(String name, long time) {
        this.name = name;
        this.time = time;
    }

    public void release() {
        System.out.println("MyWork: " + this.name + " をリリース.");
    }

    public boolean isDaemon() {
        return false;
    }

    public void run() {
        try {
            System.out.println("MyWork: " + this.name + " を開始します.");
            System.out.println("MyWork: " + this.name 
                    + " を " + this.time + " ミリ秒スリープします.");
            Thread.sleep(this.time);
            System.out.println("MyWork: " + this.name + " を終了します.");
        } catch(InterruptedException e) {
            e.printStackTrace();
        }
    }
}

最後にJNDI経由でWorkManagerを取得してSampleWorkの処理を実行するサーブレットWorkServletを作成する。ここではリスト3のようにinit()メソッドにおいてInitialContextを利用してWorkManagerオブジェクトを取得し、これを用いて2つのSampleWorkを実行するようにした。

WorkManagerのschedule()メソッドはWorkオブジェクトの状態を保持するWorkItemオブジェクトを返す。WorkItemの集合をwaitForAll()メソッドに渡すと、Thread.join()のように対象となる全てのWorkが完了するまで待ち受けるようになる。第2引数にはタイムアウト時間を指定する。ちなみにwaitForAny()メソッドの場合、対象となるWorkのどれかひとつが終了するまで待ち受ける。

リスト3 WorkServlet.java - Work Managerを利用したサーブレットの例

package sample;

import commonj.work.WorkItem;
import commonj.work.WorkManager;
import commonj.work.WorkException;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import javax.servlet.*;
import javax.servlet.http.*;

public class WorkServlet extends HttpServlet {
   WorkManager myWorkManager = null;

    @Override
    public void init() throws ServletException {
      try {
         InitialContext ic = new InitialContext();
         // WorkManagerオブジェクトをJNDI経由で取得
         this.myWorkManager
            =(WorkManager)ic.lookup("java:comp/env/wm/MyWorkManager");
      } catch(NamingException e) {
         e.printStackTrace();
      }
   } 

    protected void processRequest
                (HttpServletRequest request, HttpServletResponse response)
           throws ServletException, IOException {
        response.setContentType("text/html;charset=UTF-8");

        SampleWork work1 =null;
        SampleWork work2 =null;

        try {
            // Workオブジェクトの生成
            work1 = new SampleWork("ワーク1", 5000);
            work2 = new SampleWork("ワーク2", 8000);

            System.out.println("WorkServlet: Workを開始します.");

            // WorkオブジェクトをWorkManagerオブジェクトに登録
            WorkItem workItem1 = this.myWorkManager.schedule(work1);
            WorkItem workItem2 = this.myWorkManager.schedule(work2);

            List workItemList=new ArrayList();
            workItemList.add(workItem1);
            workItemList.add(workItem2);

            if (this.myWorkManager.waitForAll(workItemList, WorkManager.INDEFINITE)) {
                System.out.println("WorkServlet: 全てのWorkが終了しました.");
            }
            else {
                System.out.println("WorkServlet: タイムアウトしました.");
            }
        }
        catch (WorkException ex) {
            ex.printStackTrace();
        } 
        catch (InterruptedException ex) {
            ex.printStackTrace();
        } 
    } 

    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    } 

    protected void doPost(HttpServletRequest request, HttpServletResponse response)
             throws ServletException, IOException {
        processRequest(request, response);
    }
}

web.xmlにはServletを有効にするための設定を追加し、最終的にはリスト4のようになる。

リスト4 web.xml - サーブレットの設定を追加

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
                             http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">

    <resource-ref>
        <res-ref-name>wm/MyWorkManager</res-ref-name>
        <res-type>commonj.work.WorkManager</res-type>
        <res-auth>Container</res-auth>      
        <res-sharing-scope>Shareable</res-sharing-scope>
    </resource-ref>

    <servlet>
        <servlet-name>WorkServlet</servlet-name>
        <servlet-class>sample.WorkServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>WorkServlet</servlet-name>
        &lturl-pattern>/WorkServlet</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>
            30
        </session-timeout>
    </session-config>
</web-app>

完成したらコンパイルしてWARファイルにまとめ、アプリケーションサーバにデプロイする。ブラウザからWorkServletサーブレットにアクセスすると、アプリケーションサーバの標準出力にはプロンプト2.6のように表示されるはずだ。なお、今回は簡略化のためWebページ側のデザインは省略した。

プロンプト1 WorkServletの実行結

WorkServlet: Workを開始します.
MyWork: ワーク1 を開始します.
MyWork: ワーク1 を 5000 ミリ秒スリープします.
MyWork: ワーク2 を開始します.
MyWork: ワーク2 を 8000 ミリ秒スリープします.
MyWork: ワーク1 を終了します.
MyWork: ワーク2 を終了します.
WorkServlet: 全てのWorkが終了しました.

次回はCommonJで実装されているもうひとつのAPIである"Timer"に関する機能を紹介したい。こちらはJSR 236: Timer for Application Serversとして標準化が進められている。