質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

87.92%

TomcatにWebアプリをリリースした後に定期処理を一定間隔で実行し、再デプロイ時に定期処理を初期化したい

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 3,465

score 21

 前提・実現したいこと

TomcatにJavaで作ったwebアプリをデプロイし公開する。
そのwebアプリをリリースして以降、ある処理を一定間隔で実行し続けたい。
そのwebアプリをアンデプロイした際は定期処理を終了したい。
そのwebアプリを再デプロイした際は定期処理を初期化し、再度実行を開始したい。

 発生している問題

・Tomcatからwebアプリをアンデプロイした際にその処理が止まらない
・Tomcatに同じwebアプリを再デプロイした際にスレッドが重複していってしまう。

 該当のソースコード

webアプリをデプロイした際に初期処理をするクラス

public class InitialServlet extends HttpServlet {
    // 初期処理を記述して下さい。
    public void init() throws ServletException {
        ScheduleManager schedule = ScheduleManager.getInstance();
        schedule.start();
    }


定期処理を開始するクラス

public class ScheduleManager extends Thread {

    private static ScheduleManager instance = new ScheduleManager();
    private static Timer timer = new Timer();

    private ScheduleManager() {
    }

    @Override
    public synchronized void run() {
        if (timer != null) {
            // 既にtimerがあれば停止する
            timer.cancel();
            System.out.println("stop run");
        }
        // timerの開始
        System.out.println("start run");
        timer = new Timer();
        timer.schedule(Schedule.getInstance(), 0, 2000);
    }

    public static ScheduleManager getInstance() {
        return instance;
    }
}


定期処理の実装

class Schedule extends TimerTask {

    private static Schedule instance = new Schedule();
    private Schedule() {
    }
    int testCount = 0;

    @Override
    public void run() {
        System.out.println(testCount + "回目");
        testCount++;
    }

    public static Schedule getInstance() {
        return instance;
    }
}

 試したこと

ScheduleManagerクラスやScheduleクラスをシングルトンクラスにしてみたりしましたが、実行結果が以下のようになってしまいます。
webアプリを再配置する毎に処理が重複されて定期実行されてしまいます。
なお、Tomcatを再起動すればまた"1回目"から始まりますが、運用上Tomcatを止める事は出来ません。

113回目
63回目
91回目
114回目
64回目
92回目
115回目
65回目
93回目
116回目
66回目
94回目

 望んでいる実行結果

1回目
2回目
3回目
4回目←ここでwebアプリを再配置したとすると
stop run
start run
1回目
2回目
3回目
4回目

 FW/ツールのバージョンなど

apache-tomcat-7.0.78

 補足

お恥ずかしいですが独学で学んでいるため、笑ってしまうような実装があるかと思います。
出来れば今回の質問の本題以外のご指摘もありましたら是非宜しくお願いします。
また、質問内容に不備がありましたら追記致します。
お手数お掛け致しますが助言を宜しくお願いします。


内容から見るとデプロイされている間というのはそのデプロイ中(初期化処理中)ではなくリリース完了後以降(初期化終了後)と読み替えていいですか?

仰る通りです。質問内容を訂正します。

またさらに内容見ると、どちらかというと定期処理をデプロイ時に初期化したいと読み取れるのですが、あってますか?

仰る通りです。質問内容を訂正します。


以下のようなリスナーを追加したところ、もしかしたらうまく動いているかもしれません。
その他の解決方法やご指摘等ありましたら宜しくお願い致します。

public class InitializationListener implements ServletContextListener {

    public void contextInitialized(ServletContextEvent event) {
    // 起動を検知
        System.out.println("/////////////////////////////////////////////////////////////////");
        System.out.println("Initialized");
        System.out.println("/////////////////////////////////////////////////////////////////");
        ScheduleOutput.getInstance().startTimer();
    }

    @Override
    public void contextDestroyed(ServletContextEvent event) {
    //終了を検知
        System.out.println("/////////////////////////////////////////////////////////////////");
        System.out.println("Destroyed");
        System.out.println("/////////////////////////////////////////////////////////////////");
        ScheduleOutput.getInstance().stopTimer();
    }
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • fsk5303

    2018/08/07 16:21

    ①はい、仰る通り、リリース完了後以降に定期処理を行い続けるという意味です。②はい、仰る通り、デプロイ時に定期処理を初期化したいです。私の今の状態だと初期化されずに定期処理が重複されて実行されていってしまいます。

    キャンセル

  • asahina1979

    2018/08/08 00:57

    入れてない場合があるから回答じゃなくこちらへ https://tomcat.apache.org/tomcat-8.0-doc/manager-howto.html#Stop_an_Existing_Application

    キャンセル

  • asahina1979

    2018/08/08 07:48

    まあ、 InitialServlet をよくみたら destroy してないな

    キャンセル

回答 3

checkベストアンサー

+2

こんにちは、
質問の内容を、以下だと仮定して回答いたします。

・Webアプリケーション以外に定期処理(スレッド)を実行したい
・Webアプリケーション開始時に定期処理(スレッド)を開始したい
・Webアプリケーション終了時にはその定期処理(スレッド)は終了させたい

多分、ご質問の端的な答えとしてはWebアプリケーション起動時と終了時に実行されるインターフェースが
あるため、それをフィルターに設定すれば良いのですが、あまりお勧めはできません。

上記が質問意図であれば、以前自分も同じようなシステム構成に悩みました。
Webアプリケーションを起動すれば定期実行バッチスレッドも起動すればいいよねと。

結論から言うと、Webアプリ開始時にスレッドを起動、ではなく、Webアプリと
定期実行処理のプロセスを別にした方がよいです。実際にはWebアプリは普通に起動させ、
手動かどうかは置いて置きますが、スレッドを起動するバッチプログラムを別に用意します。

古い情報ですがServlet3.0からサーブレットからのスレッド起動は一応、可能と言う見解ですが、
システム的に見ればお悩みのように、Webアプリ終了時に必ずスレッドが終了するという
補償ができません。何らかの理由によりスレッドがゾンビ化してしまった場合、Webアプリケーション
のみならずTomcatも停止する必要性が出てきます。(質問に記載はありませんでしたが、その場合、
Tomcat上で動いている別アプリケーションも死にます)

そのためシステムの安全性を考えた場合Webアプリケーションとスレッド処理は別のプロセスに
分ける方法が良いと思います
上記の方法であれば、万が一Webアプリ終了時にスレッドがゾンビ化した場合、JEEコンテナ
本体の終了などをする必要もなく単にバッチプログラムのプロセス終了で良くなります。

ちょっと面倒ですが、Tomcatを起動(Webアプリをデプロイ)その後、スレッド処理を別プロセスで
起動、終了の際は、Webアプリをアンデプロイ(停止)、スレッド処理を終了と言う認識です。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/24 17:06

    とても丁寧にありがとうございます。
    都合により返信ができずにおりました。申し訳ありません。
    質問文の一番最後に追記しました通り、ServletContextListenerインターフェースを実装したInitializationListenerクラスにて、contextInitialized()メソッドとcontextDestroyed()メソッドを用意し、それらの中でスレッドの開始と終了を行う事で、一応目的は達成できました。しかし、この方法ですと、arcanum_jpさんが仰るように「Webアプリ終了時に必ずスレッドが終了するという補償ができていない」という事になるのでしょうか・・・。
    その点を保証しようとしたら、arcanum_jpさんが仰るようにプロセスを別にするしかないという事になるのでしょうか。

    キャンセル

  • 2018/08/30 19:23

    割り切りの問題かと。

    自分の場合、Webアプリ起動時ではなく、管理画面からスレッドを起動するという形でしたがご質問者が作られているWebアプリも多分アプリのどこかにスレッドへの参照を持っているのだと思います。それらが何らかのバグにより切れてしまった場合、当然スレッドが終了できません。

    そんなレアケースなど、それはテストでやればよいというのであればそれは割り切りの問題だと思いますが。TomcatにWebアプリが1つ乗っている状態であれば杞憂だと思います。

    あと、質問や回答では触れられておりませんが、スレッド内でJDBCのコネクションへのプール参照を行う場合など結構ピーキーな問題が発生しますので、そういう場合などはやはりスレッドとWebアプリは別にした方が良いと思います。

    やらない方がいい理由を述べてしまいましたが割り切りが大きいのかなと思いました。

    参考(超古いですが)
    http://frmmpgit.blog.fc2.com/blog-entry-93.html

    キャンセル

+2

Servletのinit(ServletConfig config) はデフォルトの設定だと、ホットデプロイ時には実行されません。

アプリケーションのホットデプロイ後にinitメソッドを実行させたい場合は、

@WebServlet(urlPatterns="/", loadOnStartup=1)

サーブレットにloadOnStartup属性をつけ、-1以外にするとよいでしょう。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/24 17:08

    丁寧にありがとうございます。
    都合により返信ができずにおりました。申し訳ありません。
    質問文の一番最後に追記しました通り、ServletContextListenerインターフェースを実装したInitializationListenerクラスにて、contextInitialized()メソッドとcontextDestroyed()メソッドを用意し、それらの中でスレッドの開始と終了を行う事で、一応目的は達成できました。
    A-pZさんに教えて頂いた方法をまだ試せていないので、こちらも試してみて勉強させていただきます。ありがとうございました。

    キャンセル

+1

定期処理を開始するクラスですが

if (timer != null) { としていますが
この場合
private static Timer timer = new Timer();
としているので常にTimerインスタンスが作られており、この判定でtrueとなることはありません。

ですので
private static Timer timer;
とし

if (timer != null) {
   timer.cancel();// 既にtimerがあれば停止する
   System.out.println("stop run");
   timer = null;
}

if (timer == null) {
   System.out.println("start run");
   timer = new Timer();// timerの開始
   timer.schedule(Schedule.getInstance(), 0, 2000);
}

とすれば動くと思います

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/08/07 19:00

    synchronized に修正を加えましたが、ちょっと動くか自信ないので駄目ならエラー状況教えてください。

    キャンセル

  • 2018/08/24 17:08 編集

    丁寧にありがとうございます。
    都合により返信ができずにおりました。申し訳ありません。
    質問文の一番最後に追記しました通り、ServletContextListenerインターフェースを実装したInitializationListenerクラスにて、contextInitialized()メソッドとcontextDestroyed()メソッドを用意し、それらの中でスレッドの開始と終了を行う事で、一応目的は達成できました。
    namdaさんに教えて頂いた方法をまだ試せていないので、こちらも試してみて勉強させていただきます。ありがとうございました。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 87.92%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る

  • トップ
  • Javaに関する質問
  • TomcatにWebアプリをリリースした後に定期処理を一定間隔で実行し、再デプロイ時に定期処理を初期化したい