web-dev-qa-db-ja.com

Java EE)で新しいスレッドを手動で開始しても安全ですか?

セッションスコープのJSFマネージドBean内でスレッドを生成しても安全かどうかについて、明確な答えは見つかりませんでした。スレッドは、ステートレスEJBインスタンス(マネージドBeanに依存性が注入された)のメソッドを呼び出す必要があります。

背景は、生成に時間がかかるレポートがあることです。これにより、サーバーの設定を変更できないため、HTTPリクエストがタイムアウトしました。そのため、新しいスレッドを開始してレポートを生成させ、一時的に保存するという考えです。その間、JSFページには進行状況バーが表示され、生成が完了するまで管理Beanをポーリングしてから、保存されているレポートをダウンロードするための2番目の要求を行います。これはうまくいくようですが、私がやっていることはハックではないことを確認したいと思います。

45
Dmitry Chornyi

前書き

セッションスコープのマネージドBean内からスレッドを生成することは、必要な作業を行う限り、必ずしもハックではありません。ただし、スレッドの生成自体は、細心の注意を払って行う必要があります。たとえば、1人のユーザーがセッションごとに無制限のスレッドを生成したり、セッションが破棄された後でもスレッドが実行を継続したりするような方法でコードを記述しないでください。それは遅かれ早かれあなたのアプリケーションを爆破するでしょう。

たとえば、ユーザーがセッションごとに複数のバックグラウンドスレッドを決して発生させないようにし、セッションが破棄されるたびにスレッドが中断されることが保証されるように、コードをこのように記述する必要があります。セッション内の複数のタスクについては、タスクをキューに入れる必要があります。また、アプリケーションレベルで生成されるスレッドの合計量に制限を設けることができるように、これらのスレッドはすべて、共通のスレッドプールによって提供されることが望ましいです。

したがって、スレッドの管理は非常にデリケートな作業です。そのため、new Thread()やフレンドを使用して独自に自作するのではなく、組み込みの機能を使用する方がよいでしょう。平均Java EEアプリケーションサーバーは、EJBの中で利用できるコンテナー管理スレッドプールを提供します @Asynchronous および @Schedule 。コンテナに依存しないようにするには(読み:Tomcatに対応)、Java 1.5のUtil Concurrent ExecutorService および ScheduledExecutorService これには。

以下の例では、Java EE 6+ with EJBを想定しています。

フォーム送信時にタスクを起動して忘れる

@Named
@RequestScoped // Or @ViewScoped
public class Bean {

    @EJB
    private SomeService someService;

    public void submit() {
        someService.asyncTask();
        // ... (this code will immediately continue without waiting)
    }

}
@Stateless
public class SomeService {

    @Asynchronous
    public void asyncTask() {
        // ...
    }

}

ページの読み込み時にモデルを非同期でフェッチする

@Named
@RequestScoped // Or @ViewScoped
public class Bean {

    private Future<List<Entity>> asyncEntities;

    @EJB
    private EntityService entityService;

    @PostConstruct
    public void init() {
        asyncEntities = entityService.asyncList();
        // ... (this code will immediately continue without waiting)
    }

    public List<Entity> getEntities() {
        try {
            return asyncEntities.get();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            throw new FacesException(e);
        } catch (ExecutionException e) {
            throw new FacesException(e);
        }
    }
}
@Stateless
public class EntityService {

    @PersistenceContext
    private EntityManager entityManager;

    @Asynchronous
    public Future<List<Entity>> asyncList() {
        List<Entity> entities = entityManager
            .createQuery("SELECT e FROM Entity e", Entity.class)
            .getResultList();
        return new AsyncResult<>(entities);
    }

}

JSFユーティリティライブラリ OmniFaces を使用している場合、マネージドBeanに @Eager

アプリケーションの開始時にバックグラウンドジョブをスケジュールする

@Singleton
public class BackgroundJobManager {

    @Schedule(hour="0", minute="0", second="0", persistent=false)
    public void someDailyJob() {
        // ... (runs every start of day)
    }

    @Schedule(hour="*/1", minute="0", second="0", persistent=false)
    public void someHourlyJob() {
        // ... (runs every hour of day)
    }

    @Schedule(hour="*", minute="*/15", second="0", persistent=false)
    public void someQuarterlyJob() {
        // ... (runs every 15th minute of hour)
    }

    @Schedule(hour="*", minute="*", second="*/30", persistent=false)
    public void someHalfminutelyJob() {
        // ... (runs every 30th second of minute)
    }

}

アプリケーション全体のモデルをバックグラウンドで継続的に更新する

@Named
@RequestScoped // Or @ViewScoped
public class Bean {

    @EJB
    private SomeTop100Manager someTop100Manager;

    public List<Some> getSomeTop100() {
        return someTop100Manager.list();
    }

}
@Singleton
@ConcurrencyManagement(BEAN)
public class SomeTop100Manager {

    @PersistenceContext
    private EntityManager entityManager;

    private List<Some> top100;

    @PostConstruct
    @Schedule(hour="*", minute="*/1", second="0", persistent=false)
    public void load() {
        top100 = entityManager
            .createNamedQuery("Some.top100", Some.class)
            .getResultList();
    }

    public List<Some> list() {
        return top100;
    }

}

以下も参照してください。

44
BalusC

EJB 3.1 @Asynchronous methodsを確認してください。これがまさに彼らの目的です。

OpenEJB 4.0.0-SNAPSHOTを使用する小さな例。ここに@Singleton Beanがあり、@Asynchronousとマークされた1つのメソッドがあります。そのメソッドがだれか(この場合はJSF管理対象Bean)によって呼び出されるたびに、メソッドが実際にかかる時間に関係なく、すぐに戻ります。

@Singleton
public class JobProcessor {

    @Asynchronous
    @Lock(READ)
    @AccessTimeout(-1)
    public Future<String> addJob(String jobName) {

        // Pretend this job takes a while
        doSomeHeavyLifting();

        // Return our result
        return new AsyncResult<String>(jobName);
    }

    private void doSomeHeavyLifting() {
        try {
            Thread.sleep(SECONDS.toMillis(10));
        } catch (InterruptedException e) {
            Thread.interrupted();
            throw new IllegalStateException(e);
        }
    }
}

これは、@Asynchronousメソッドを連続して数回呼び出す小さなテストケースです。

各呼び出しは Future オブジェクトを返します。これは基本的にemptyで始まり、後で関連するときにコンテナによって値が入力されますメソッド呼び出しは実際に完了します。

import javax.ejb.embeddable.EJBContainer;
import javax.naming.Context;
import Java.util.concurrent.Future;
import Java.util.concurrent.TimeUnit;

public class JobProcessorTest extends TestCase {

    public void test() throws Exception {

        final Context context = EJBContainer.createEJBContainer().getContext();

        final JobProcessor processor = (JobProcessor) context.lookup("Java:global/async-methods/JobProcessor");

        final long start = System.nanoTime();

        // Queue up a bunch of work
        final Future<String> red = processor.addJob("red");
        final Future<String> orange = processor.addJob("orange");
        final Future<String> yellow = processor.addJob("yellow");
        final Future<String> green = processor.addJob("green");
        final Future<String> blue = processor.addJob("blue");
        final Future<String> Violet = processor.addJob("Violet");

        // Wait for the result -- 1 minute worth of work
        assertEquals("blue", blue.get());
        assertEquals("orange", orange.get());
        assertEquals("green", green.get());
        assertEquals("red", red.get());
        assertEquals("yellow", yellow.get());
        assertEquals("Violet", Violet.get());

        // How long did it take?
        final long total = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start);

        // Execution should be around 9 - 21 seconds
        assertTrue("" + total, total > 9);
        assertTrue("" + total, total < 21);
    }
}

サンプルソースコード

カバーの下でこの作業を行うものは次のとおりです。

  • 呼び出し側が見るJobProcessorは、実際にはJobProcessorのインスタンスではありません。むしろ、すべてのメソッドがオーバーライドされているサブクラスまたはプロキシです。非同期であることになっているメソッドは、異なる方法で処理されます。
  • 非同期メソッドを呼び出すと、指定したメソッドとパラメーターをラップするRunnableが作成されます。この実行可能ファイルは Executor に与えられます。これは単にスレッドプールに接続された作業キューです。
  • 作業をキューに追加した後、プロキシバージョンのメソッドは、現在キューで待機しているFutureにリンクされているRunnableの実装を返します。
  • RunnablerealJobProcessorインスタンスで最後にメソッドを実行すると、戻り値を取得して設定しますFutureに入れて、呼び出し元が利用できるようにします。

AsyncResultが返すJobProcessorオブジェクトは、呼び出し元が保持しているFutureオブジェクトとは異なることに注意してください。実際のJobProcessorStringを返すだけで、呼び出し元のJobProcessorFuture<String>を返すことができれば、それはすばらしいことでしたが、方法はわかりませんでした複雑さを増すことなくそれを行うために。したがって、AsyncResultは単純なラッパーオブジェクトです。コンテナはStringを引き出し、AsyncResultを捨て、StringrealFuture呼び出し元が保持していること。

途中で進捗状況を取得するには、単純に AtomicInteger のようなスレッドセーフオブジェクトを@Asynchronousメソッドに渡し、Beanコードで完了率を定期的に更新します。

53
David Blevins